2009-04-08 13 views
162

Si BaseFruit tiene un constructor que acepta un int weight, ¿puedo crear una instancia de una fruta en un método genérico como este?Crear instancia de tipo genérico?

public void AddFruit<T>()where T: BaseFruit{ 
    BaseFruit fruit = new T(weight); /*new Apple(150);*/ 
    fruit.Enlist(fruitManager); 
} 

Se agrega un ejemplo detrás de los comentarios. Parece que solo puedo hacer esto si le doy a BaseFruit un constructor sin parámetros y luego lleno todo a través de las variables de los miembros. En mi código real (no sobre la fruta) esto es bastante poco práctico.

-Update-
por lo que parece que no puede ser resuelto por las restricciones de ninguna manera a continuación. A partir de las respuestas hay tres soluciones candidatas:

  • patrón de la fábrica
  • Reflexión
  • Activador

tiendo a pensar que la reflexión es la menos una limpia, pero no puede decidir entre el otros dos.

+0

BTW: hoy probablemente resolvería esto con la biblioteca IoC de elección. –

Respuesta

238

Además un ejemplo más simple:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight }); 

Tenga en cuenta que el uso de la nueva restricción() en T es sólo para hacer el compilador compruebe si hay un constructor público sin parámetros en tiempo de compilación, el código real utilizado para crear el tipo es la clase Activator.

Deberá asegurarse de que el constructor específico existente, y este tipo de requisito puede ser un olor a código (o algo que simplemente debe evitar en la versión actual en C#).

+0

Como este constructor está en la clase base (BaseFruit), sé que tendrá un constructor. Pero, de hecho, si algún día decido que la fruta base necesita más parámetros, podría ser jodido. Veremos en la clase ACtivator sin embargo. No he oído hablar de eso antes. –

+3

Este salió bien. También hay un procedimiento CreateInstance (), pero que no tiene una sobrecarga para los parámetros de algunos rason .. –

+8

No es necesario usar 'new object [] {weight}'. 'CreateInstance' se declara con params,' public static object CreateInstance (Type type, params object [] args) ', por lo que puede hacer' return (T) Activator.CreateInstance (typeof (T), weight); '. Si hay múltiples parámetros, páselos como argumentos separados. Solo si ya tiene un enumerable enumerado de parámetros debería molestarse en convertirlo a 'object []' y pasarlo a 'CreateInstance'. – ErikE

40

Sí; cambiar de dónde sea:

where T:BaseFruit, new() 

Sin embargo, esto sólo funciona con sin parámetros constructores. Deberá tener otros medios para configurar su propiedad (establecer la propiedad en sí o algo similar).

+42

Maldición, tengo a Robinsoned ... –

+3

@Jon Skeet: Eso estuvo muy cerca de hacerme reír a carcajadas (¡en el trabajo!). –

+1

@MichaelMyers No era tan afortunado – ppeterka

74

No puede usar ningún constructor con parámetros. Puede usar un constructor sin parámetros si tiene una restricción "where T : new()".

es un dolor, pero así es la vida :(

Ésta es una de las cosas que me gustaría abordar con "static interfaces". A continuación, sería capaz de limitar T para incluir métodos estáticos, operadores y constructores , y luego llamarlos.

+1

¡Eres todo afrutado! :) –

+10

Realmente solo quería decir Skeeted. –

+1

Al menos PUEDE hacer tales restricciones: Java siempre me decepciona. –

13

Como Jon señaló esta es la vida para restringir un constructor no sin parámetros. sin embargo, una solución diferente es usar un patrón de fábrica. esto es fácilmente constrainable

interface IFruitFactory<T> where T : BaseFruit { 
    T Create(int weight); 
} 

public void AddFruit<T>(IFruitFactory<T> factory) where T: BaseFruit {  
    BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/  
    fruit.Enlist(fruitManager); 
} 

sin embargo, otra opción es usar un enfoque funcional. Pase en un método de fábrica.

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
    BaseFruit fruit = factoryDel(weight); /* new Apple(150); */ 
    fruit.Enlist(fruitManager); 
} 
+2

Buena sugerencia, aunque si no tiene cuidado puede terminar en el infierno de la API DOM de Java, con muchas fábricas :( –

+0

@Jon, no querría eso :) – JaredPar

+0

Sí, esta es una solución que estaba considerando mí mismo. Pero esperaba algo en la línea de restricciones. Adivina, entonces no ... –

10

Usted puede hacer mediante el uso de la reflexión:

public void AddFruit<T>()where T: BaseFruit 
{ 
    ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) }); 
    if (constructor == null) 
    { 
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor"); 
    } 
    BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit; 
    fruit.Enlist(fruitManager); 
} 

EDIT: Agregado constructor == cheque nulo.

EDIT: Una variante más rápido usando una caché:

public void AddFruit<T>()where T: BaseFruit 
{ 
    var constructor = FruitCompany<T>.constructor; 
    if (constructor == null) 
    { 
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor"); 
    } 
    var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit; 
    fruit.Enlist(fruitManager); 
} 
private static class FruitCompany<T> 
{ 
    public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) }); 
} 
+0

Aunque no me gusta la sobrecarga de la reflexión, como otros han explicado, esta es la forma en que es actualmente. Viendo que este constructor no se llamará demasiado, podría ir con esto. O la fábrica. No lo sé todavía –

17

La solución más sencilla Activator.CreateInstance<T>()

+0

Directo al punto! tienes mi voto –

0

Recientemente me encontré con un problema muy similar. Solo quería compartir nuestra solución con todos ustedes. Quería que he creado una instancia de una Car<CarA> de un objeto JSON con el que tenía una enumeración:

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>(); 

mapper.Add(1, typeof(CarA)); 
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class 
{  
    public T Detail { get; set; } 
    public Car(T data) 
    { 
     Detail = data; 
    } 
} 
public class CarA 
{ 
    public int PropA { get; set; } 
    public CarA(){} 
} 
public class CarB 
{ 
    public int PropB { get; set; } 
    public CarB(){} 
} 

var jsonObj = {"Type":"1","PropA":"10"} 
MyEnum t = GetTypeOfCar(jsonObj); 
Type objectT = mapper[t] 
Type genericType = typeof(Car<>); 
Type carTypeWithGenerics = genericType.MakeGenericType(objectT); 
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) }); 
-2

Todavía es posible, con un alto rendimiento, haciendo lo siguiente:

// 
    public List<R> GetAllItems<R>() where R : IBaseRO, new() { 
     var list = new List<R>(); 
     using (var wl = new ReaderLock<T>(this)) { 
      foreach (var bo in this.items) { 
       T t = bo.Value.Data as T; 
       R r = new R(); 
       r.Initialize(t); 
       list.Add(r); 
      } 
     } 
     return list; 
    } 

y

// 
///<summary>Base class for read-only objects</summary> 
public partial interface IBaseRO { 
    void Initialize(IDTO dto); 
    void Initialize(object value); 
} 

Las clases relevantes tienen que derivarse de esta interfaz e inicializarse en consecuencia. Tenga en cuenta que, en mi caso, este código es parte de una clase circundante, que ya tiene <T> como parámetro genérico. R, en mi caso, también es una clase de solo lectura. IMO, la disponibilidad pública de las funciones de Initialize() no tiene un efecto negativo en la inmutabilidad. El usuario de esta clase podría incluir otro objeto, pero esto no modificaría la colección subyacente.

Cuestiones relacionadas