multiple - template c#
Problema genérico de C#: actualización del tipo genérico con parámetros en el constructor (7)
¿Has considerado usar Activator (esta es solo otra opción)?
T homepageMgmtCarouselItem = Activator.CreateInstance(typeof(T), pageData) as T;
Estoy tratando de crear una clase genérica que sea una instancia del tipo genérico. Como sigue:
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:
no puede proporcionar argumentos al crear una instancia de un tipo de variable
Encontré la siguiente pregunta relacionada que está muy cerca de lo que necesito: Pasar argumentos a C # genérico new () de tipo de plantilla
Sin embargo, no puedo usar la respuesta sugerida de Jared ya que estoy llamando al método dentro de la clase Genérica, no fuera de él, así que no puedo especificar la clase concreta.
¿Hay alguna forma de evitar esto?
He intentado lo siguiente en base a la otra pregunta, pero no funciona porque no sé el tipo concreto de T para especificar. Como se llama desde dentro de la clase genérica, no desde afuera:
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;
}
}
******** EDITAR: POSIBLES POSIBLES SOLUCIONES **
Así que he probado 2 posibles soluciones:
Primero es exactamente como se explica a continuación por Jon Skeet . Esto definitivamente funciona pero significa tener una lambda oscura en el constructor. No me siento muy cómodo con esto, ya que significa que los usuarios necesitan saber la lambda correcta que se espera. Después de todo, podrían pasar un lambda que no se inscribe en el tipo, pero hace algo totalmente inesperado.
En segundo lugar, fui por la ruta del método Factory; Agregué un método Crear a la interfaz común:
IJewellerHomepageCarouselItem Create(PageData pageData);
Luego proporcionó una implementación en cada clase concreta:
public IJewellerHomepageCarouselItem Create(PageData pageData)
{
return new JewellerHomepageCarouselItem(pageData, null);
}
Y usé una sintaxis de inicialización de dos pasos:
T carouselItem = new T();
T homepageMgmtCarouselItem = (T) carouselItem.Create(jewellerPage);
Me encantaría escuchar algunos comentarios sobre el mérito de cada uno de estos enfoques.
¿Por qué no pones un método estático de "constructor" en la interfaz? Un poco hacky, lo sé, pero tienes que hacer lo que tienes que hacer ...
Es un handicap de C # y CLR, no puede pasar un argumento a la nueva T (), simple.
Si viene de un fondo de C ++, esto NO debe estar roto ni TRIVIAL. ADEMÁS, ni siquiera necesita una interfaz / restricción. Se rompe por todas partes, y sin ese hack funcional de fábrica 3.0, se ve obligado a realizar una inicialización de 2 pases. Gestionado la blasfemia!
Haga la nueva T () primero y luego establezca la propiedad o pase una sintaxis inicializadora exótica o, como se sugiere, use la solución funcional del Pony. Todo está bien, pero esa es la idea de compilación y tiempo de ejecución de los "genéricos" para usted.
Hay otra solución posible, bastante sucia.
Haga que IHomepageCarouselItem tenga el método "Construir" que toma pageData como parámetro y devuelve IHomepageCarouselItem.
Entonces haz esto:
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;
La respuesta de Jared sigue siendo una buena forma de Func<PageData, T>
: solo necesitas hacer que el constructor tome la Func<PageData, T>
y la Func<PageData, T>
para más tarde:
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;
}
Luego simplemente pase la función al constructor donde crea la nueva instancia de HomepageCarousel<T>
.
(Recomiendo la composición en lugar de la herencia, por cierto ... derivarse de la List<T>
es casi siempre el camino equivocado).
Probablemente aceptaría la sugerencia de Tony "jon", el pony Skeet, pero hay otra forma de hacerlo. Entonces, principalmente para la diversión, aquí hay una solución diferente (que tiene el lado negativo de fallar en el tiempo de ejecución si se olvida de implementar el método necesario pero la ventaja de no tener que suministrar un método de fábrica, el compilador lo conectará mágicamente).
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();
}
}
solo para repetir mi "descargo de responsabilidad", es mucho más que servir como un recordatorio de que puede haber enfoques bastante diferentes para resolver el mismo problema, todos ellos tienen desventajas y puntos fuertes. Una desventaja de este enfoque es que es en parte magia negra;)
T homepageMgmtCarouselItem = null;
homepageMgmtCarouselItem = homepageMgmtCarouselItem.create(pageData);
pero evitas que el constructor peculiar tenga un argumento delegado. (pero por lo general opto por ese enfoque, a menos que esté usando un mecanismo de inyección de dependencia para suministrarme la clase de fábrica para mí. Lo que de manera incidental es el tipo de marco de DI en el que estoy trabajando en mi tiempo libre; p)
Solo para agregar a otras respuestas:
Lo que estás haciendo aquí se llama básicamente proyección . Tiene una List
de un tipo y desea proyectar cada elemento (con un delegado) a un tipo de elemento diferente.
Entonces, una secuencia general de operaciones es en realidad (usando 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, puede 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 se ve el resto del código, pero creo que GetInitialCarouselData
no es el lugar correcto para manejar la inicialización, especialmente porque básicamente está duplicando 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 ahora tu clase tiene un constructor como este:
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, porque está accediendo a un campo de jewellerHomepages
en su método (así que supongo que lo está almacenando en ctor).
Hay varios problemas con este enfoque.
Usted tiene una referencia a
jewellerHomepages
que es innecesario. Su lista es una lista de IHomepageCarouselItems, por lo que los usuarios pueden simplemente llamar al método Clear () y rellenarlo con lo que quieran. Entonces terminas con una referencia a algo que no estás usando.Podrías arreglar eso simplemente eliminando el campo:
public class HomepageCarousel(List<PageData> jewellerHomepages) { // do not save the reference to jewellerHomepages this.AddRange(GetInitialCarouselData(jewellerHomepages)); }
Pero, ¿qué sucede si te das cuenta de que es posible que desees inicializarlo utilizando alguna otra clase, diferente de
PageData
? En este momento, estás creando la lista como esta:HomepageCarousel<ConcreteCarousel> list = new HomepageCarousel<ConcreteCarousel>(listOfPageData);
¿Te estás dejando alguna opción para crear una instancia con cualquier otra cosa algún día? Incluso si agrega un nuevo
GetInitialCarouselData
, su métodoGetInitialCarouselData
todavía es demasiado específico para usar soloPageData
como fuente.
La conclusión es: No use un tipo específico en su constructor si no hay necesidad de hacerlo. Crear elementos de lista reales (casos concretos) en otro lugar.