2009-11-05 15 views
17

Estoy tratando de crear una clase genérica que nueva crea una instancia del tipo genérico. En la siguiente manera:C# genéricos problema - revivir el tipo genérico con parámetros en el constructor

public class HomepageCarousel<T> : List<T> 
    where T: IHomepageCarouselItem, new() 
{ 
    private List<T> GetInitialCarouselData() 
    { 
     List<T> carouselItems = new List<T>(); 

     if (jewellerHomepages != null) 
     { 
      foreach (PageData pageData in jewellerHomepages) 
      { 
       T item = new T(pageData); // this line wont compile 
       carouselItems.Add(item); 
      } 
     } 
     return carouselItems; 
    } 
} 

Pero me sale el siguiente error:

cannot provide arguments when creating an instance of a variable type

me encontré con la siguiente pregunta relacionada que está muy cerca de lo que necesito: Passing arguments to C# generic new() of templated type

Sin embargo, no puedo Utilicé la respuesta sugerida de Jared como soy llamando al método dentro de la clase genérica, no fuera de , por lo que no puedo especificar la clase concreta.

¿Hay alguna forma de evitar esto?

He intentado lo siguiente basado en la otra pregunta, pero no funciona, ya que no sé el tipo concreto de T a especifique. Como se le llama desde dentro de la clase genérica, no fuera:

public class HomepageCarousel<T> : List<T> 
    where T: IHomepageCarouselItem, new() 
{ 

    private List<T> LoadCarouselItems() 
    { 
     if (IsCarouselConfigued) 
     { 
      return GetConfiguredCarouselData(); 
     } 

     // ****** I don't know the concrete class for the following line, 
     //  so how can it be instansiated correctly? 

     return GetInitialCarouselData(l => new T(l)); 
    } 

    private List<T> GetInitialCarouselData(Func<PageData, T> del) 
    { 
     List<T> carouselItems = new List<T>(); 

     if (jewellerHomepages != null) 
     { 
      foreach (PageData pageData in jewellerHomepages) 
      { 
       T item = del(pageData); 
       carouselItems.Add(item); 
      } 
     } 
     return carouselItems; 
    } 
} 

******** EDIT: Añadido POSIBLES SOLUCIONES **

Así que he probado 2 soluciones posibles:

Primero es exactamente como se explica a continuación por Jon Skeet. Este definitivamente funciona pero significa tener un Lambda oscuro en el constructor . No me siento cómodo con esto, ya que significa que los usuarios de deben conocer la lambda correcta que se espera. Después de todo, podrían pasar un lambda que no tiene el tipo , pero hace algo completamente inesperado

En segundo lugar, seguí la ruta del método de fábrica; he añadido un método de creación de la interfaz común:

IJewellerHomepageCarouselItem Create(PageData pageData); 

Entonces proporciona una implementación en cada clase concreta:

public IJewellerHomepageCarouselItem Create(PageData pageData) 
{ 
    return new JewellerHomepageCarouselItem(pageData, null); 
} 

Y utiliza una sintaxis de inicialización de dos pasos:

T carouselItem = new T(); 
T homepageMgmtCarouselItem = (T) carouselItem.Create(jewellerPage); 

Would Me encantaría escuchar algunos comentarios sobre el mérito de cada uno de estos enfoques.

+0

posible duplicado de [Pasar argumentos a C# genérico nuevo() del tipo de plantilla] (http://stackoverflow.com/questions/840261/passing -arguments-to-c-sharp-generic-new-of-templated-type) – nawfal

Respuesta

18

respuesta de Jared sigue siendo una buena manera de ir - sólo tiene que hacer que el constructor toma la Func<PageData, T> y esconderla para más adelante:

public class HomepageCarousel<T> : List<T> where T: IHomepageCarouselItem 
{ 
    private readonly Func<PageData, T> factory; 

    public HomepageCarousel(Func<PageData, T> factory) 
    { 
     this.factory = factory; 
    } 

    private List<T> GetInitialCarouselData() 
    { 
     List<T> carouselItems = new List<T>(); 

     if (jewellerHomepages != null) 
     { 
      foreach (PageData pageData in jewellerHomepages) 
      { 
       T homepageMgmtCarouselItem = factory(pageData); 
       carouselItems.Add(homepageMgmtCarouselItem); 
      } 
     } 
     return carouselItems; 
    } 

A continuación, sólo tiene que pasar a la función en el constructor donde se crea la nueva instancia del HomepageCarousel<T>.

(recomiendo composición en lugar de la herencia, por cierto ... que deriva de List<T> es casi siempre el camino equivocado.)

+1

Nunca me ha gustado esta forma de hacerlo, y generalmente prefiero la técnica Activator (como sugirió Quintin) a menos que el uso de la reflexión vaya a tener un impacto de rendimiento inaceptable. – philsquared

+0

gracias Tony. ¿Puede explicar cómo usaría la composición en lugar de la herencia? Inicialmente tenía una propiedad llamada CarouselItems que contenía los datos. Pero luego cambió la clase para heredar de la lista e hizo que los datos estuvieran disponibles de esa manera.
Supongo que está diciendo que ambas formas no son buenas. – ChrisCa

+3

No es el impacto en el rendimiento que me importa, es el aspecto de "no descubrir que las cosas están rotas hasta el momento de la ejecución". –

18

Ha considerado el uso del activador (esto es sólo otra opción).

T homepageMgmtCarouselItem = Activator.CreateInstance(typeof(T), pageData) as T; 
+1

sí, lo he considerado. Leí este artículo. http://www.dalun.com/blogs/05.27.2007.htm Pero preferiría no ir por ese camino si se puede evitar. Me gusta la sintaxis sugerida en la otra pregunta Pero gracias de todos modos por la sugerencia – ChrisCa

0

Existe otra solución posible, bastante sucia.

Haga que IHomepageCarouselItem tenga el método "Construir" que tome pageData como parámetro y devuelva IHomepageCarouselItem.

Luego haga lo siguiente:

T factoryDummy = new T(); 
    List<T> carouselItems = new List<T>(); 

    if (jewellerHomepages != null) 
    { 
     foreach (PageData pageData in jewellerHomepages) 
     { 
      T homepageMgmtCarouselItem = (T)factoryDummy.Construct(pageData); 
      carouselItems.Add(homepageMgmtCarouselItem); 
     } 
    } 
    return carouselItems; 
1

Es un C# y minusvalía CLR, no se puede pasar un argumento a la nueva T(), simple.

Si vienes de un fondo C++, este NO debe ser roto y TRIVIAL. ADEMÁS, ni siquiera necesita una interfaz/restricción. Rotura por todas partes, y sin ese truco funcional de fábrica 3.0 se ve obligado a realizar una inicialización de 2 pasos. ¡Gestionado Blasfemia!

Primero haga la nueva T() y luego establezca la propiedad o pase una sintaxis de inicializador exótica o como bien sugirió utilizar la solución funcional del Pony. Todo asqueroso, pero esa es la idea del compilador y del tiempo de ejecución de los "genéricos" para usted.

+1

El objetivo de los genéricos es ser genérico. Realizar una implementación con requisitos específicos para escribir la implementación, como esperar que un constructor con ciertos argumentos no sea genérico y, por lo tanto, no esté permitido para los genéricos. Esa regla asegura que los genéricos son en realidad genéricos –

+1

@Rune FS: Entonces, ¿por qué puedes colocar otras restricciones sobre ellos como clases base o clase/estructura? – RCIX

+1

@ rama-jka toti: finalmente alguien llamó a la pala una espada. Cualquiera que provenga del fondo de C++ es simplemente relegado por esto. – andriej

0

Probablemente vaya por la sugerencia de Tony "jon" el pony Skeet, pero hay otra manera de hacerlo. Así que principalmente por diversión aquí hay una solución diferente (que tiene el inconveniente de fallar en tiempo de ejecución si olvida implementar el método necesario, pero la ventaja de no tener que proporcionar un método de fábrica, el compilador mágicamente lo conectará.

public class HomepageCarousel<T> : List<T> where T: IHomepageCarouselItem 
{ 

    private List<T> GetInitialCarouselData() 
    { 
     List<T> carouselItems = new List<T>(); 

     if (jewellerHomepages != null) 
     { 
      foreach (PageData pageData in jewellerHomepages) 
      { 
       T homepageMgmtCarouselItem = null; 
       homepageMgmtCarouselItem = homepageMgmtCarouselItem.create(pageData); 
       carouselItems.Add(homepageMgmtCarouselItem); 
      } 
     } 
     return carouselItems; 
    } 
} 

public static class Factory 
{ 
    someT create(this someT, PageData pageData) 
    { 
     //implement one for each needed type 
    } 

    object create(this IHomepageCarouselItem obj, PageData pageData) 
    { 
     //needed to silence the compiler 
     throw new NotImplementedException(); 
    } 
} 

simplemente para repetir mi "exención de responsabilidad", esto es mucho más que un recordatorio de que puede haber enfoques bastante diferentes para resolver el mismo problema, todos tienen inconvenientes y puntos fuertes. Una desventaja de este enfoque es que es parte magia negra;)

T homepageMgmtCarouselItem = null; 
homepageMgmtCarouselItem = homepageMgmtCarouselItem.create(pageData); 

pero se evita el constructor habitual tomando un argumento de delegado. (pero generalmente utilizo ese enfoque a menos que estuviera usando un mecanismo de inyección de dependencia para suministrar la clase de fábrica para mí. Que internamente es el tipo de marco DI en el que estoy trabajando en mi tiempo libre; p)

+0

Si obtuve este correcto, esto requeriría escribir un enorme bloque de if/else/else/else ... dentro del método de extensión Crear para manejar cada tipo correctamente? ¿Cuál es el punto en el uso de genéricos, entonces? – Groo

+0

He intentado algo similar - ver la publicación de edición. ¿Qué piensas? – ChrisCa

+0

@Groo no necesitas un gran if-else. Necesitarás un método de extensión específico para cada tipo. –

5

Solo para agregar a otras respuestas:

Lo que está haciendo aquí se llama básicamente proyección. Tiene un List de un tipo y desea proyectar cada elemento (utilizando un delegado) a un tipo de elemento diferente.

Por lo tanto, una secuencia general de operaciones es en realidad (utilizando LINQ):

// get the initial list 
List<PageData> pageDataList = GetJewellerHomepages(); 

// project each item using a delegate 
List<IHomepageCarouselItem> carouselList = 
     pageDataList.Select(t => new ConcreteCarousel(t)); 

O, si está utilizando .Net 2.0, se podría escribir una clase de ayuda como:

public class Project 
{ 
    public static IEnumerable<Tdest> From<Tsource, Tdest> 
     (IEnumerable<Tsource> source, Func<Tsource, Tdest> projection) 
    { 
     foreach (Tsource item in source) 
      yield return projection(item); 
    } 
} 

y luego usarlo como:

// get the initial list 
List<PageData> pageDataList = GetJewellerHomepages(); 

// project each item using a delegate 
List<IHomepageCarouselItem> carouselList = 
     Project.From(pageDataList, 
      delegate (PageData t) { return new ConcreteCarousel(t); }); 

no estoy seguro de cómo el resto del código es similar, pero creo que 012.no es el lugar correcto para manejar la inicialización, especialmente porque básicamente se trata de duplicar la funcionalidad de proyección (que es bastante genérica y se puede extraer en una clase separada, como Project).

[Editar] Piense en lo siguiente:

creo que en este momento su clase tiene un constructor de esta manera:

public class HomepageCarousel<T> : List<T> 
    where T: IHomepageCarouselItem, new() 
{ 
    private readonly List<PageData> jewellerHomepages; 
    public class HomepageCarousel(List<PageData> jewellerHomepages) 
    { 
     this.jewellerHomepages = jewellerHomepages; 
     this.AddRange(GetInitialCarouselData()); 
    } 

    // ... 
} 

supongo que este es el caso, ya que tiene acceso a un jewellerHomepages campo en su método (así que supongo que lo está almacenando en ctor).

Existen varios problemas con este enfoque.

  • Tiene una referencia a jewellerHomepages que no es necesaria. Su lista es una lista de IHomepageCarouselItems, por lo que los usuarios pueden simplemente llamar al método Clear() y completarlo con todo lo que deseen. Luego terminas con una referencia a algo que no estás usando.

  • Se podría arreglar eso por la simple eliminación del campo:

    public class HomepageCarousel(List<PageData> jewellerHomepages) 
    { 
        // do not save the reference to jewellerHomepages 
        this.AddRange(GetInitialCarouselData(jewellerHomepages)); 
    } 
    

    Pero lo que sucede si se da cuenta de que es posible que desee inicializar utilizando alguna otra clase, diferente de PageData? En este momento, se está creando la lista como esta:

    HomepageCarousel<ConcreteCarousel> list = 
        new HomepageCarousel<ConcreteCarousel>(listOfPageData); 
    

    se puede mostrar el mismo ninguna opción para crear una instancia que con cualquier otra cosa un día? Incluso si agrega un nuevo compuesto, su método GetInitialCarouselData sigue siendo demasiado específico para usar solo PageData como fuente.

Conclusión: no utilice un tipo específico en su constructor si no lo necesita. Crea elementos de lista reales (instancias concretas) en otro lugar.

+0

gracias por la sugerencia - ver la publicación de edición. ¿Qué piensas? – ChrisCa

+0

Creo que la clase 'HomepageCarousel ' sabe demasiado sobre el resto del mundo (violando el Principio de Responsabilidad Individual). Si tiene alguna funcionalidad adjunta a la interfaz ** IHomepageCarouselItem **, entonces debe manejar ** solo esa ** funcionalidad en su clase. La implementación real se debe delegar a la persona que llama a la clase (como se muestra en la respuesta de Jon), o simplemente puede crear instancias apropiadas en otro lugar. Después de todo, esto es simplemente una Lista de elementos (probablemente con alguna funcionalidad relacionada con IHomepageCarouselItem). ¿Por qué 'List ' alguna vez necesitaría crear instancias de 'T'? – Groo

+0

Necesita crear instancias de T para completar la lista es decir para hacer que la Lista tenga algunos datos en – ChrisCa

0

¿Por qué no pone simplemente un método estático de "constructor" en la interfaz? Un poco loco, lo sé, pero tienes que hacer lo que tienes que hacer ...

Cuestiones relacionadas