2009-11-30 27 views
19

tengo el siguiente código # ensayo C:C#: ¿tipos genéricos que tienen un constructor?

class MyItem 
    { 
    MyItem(int a) {} 
    } 

    class MyContainer<T> 
    where T : MyItem, new() 
    { 
    public void CreateItem() 
    { 
     T oItem = new T(10); 
    } 
    } 

Visual Studio no puede compilarlo, el error está en línea en la 'nueva' se utiliza:

'T': cannot provide arguments when creating an instance of a variable type 

¿Es posible en C# para crear un objeto de tipo genérico con un constructor sin parámetros? No es problema hacerlo en plantillas C++, así que tengo mucha curiosidad de por qué no puedo hacer lo mismo en C#. ¿Tal vez se requiera algún 'dónde' adicional o la sintaxis es diferente?

Respuesta

16

Se puede hacer con la reflexión:

public void CreateItem() 
{ 
    int constructorparm1 = 10; 
    T oItem = Activator.CreateInstance(typeof(T), constructorparm1) as T; 
} 

Pero no hay ninguna limitación genérica para asegurar que T implementa el constructor deseada, por lo No aconsejaría hacerlo a menos que tenga cuidado de declarar ese constructor en cada tipo que implemente la interfaz.

+0

Como recuerdo, la restricción 'new()' se traduce en una llamada 'Activator.CreateInstance()'. –

9

No existe tal limitación genérica, por lo que no es posible directamente (esta es una limitación de CLR). Si quiere esto, debe proporcionar una clase de fábrica (que tiene un constructor sin parámetros) y pasarla como un segundo parámetro de tipo genérico.

25

C# y VB.Net no admiten la noción de obligar a un genérico a tener un constructor con parámetros específicos. Solo admite restricciones para tener un constructor vacío.

Una alternativa es hacer que la persona que llama pase una lambda de fábrica para crear el valor. Por ejemplo

public void CreateItem(Func<int,T> del) { 
    T oItem = del(10); 
} 

sitio Call

CreateItem(x => new SomeClass(x)); 
+0

Muy inteligente enfoque! – Greg

2

Un patrón utilizo es tener la clase restringida implementar una interfaz que define un método Init con la firma apropiada:

interface IMyItem 
{ 
    void Init(int a); 
} 

class MyItem : IMyItem 
{ 
    MyItem() {} 
    void Init(int a) { } 
}  

class MyContainer<T> 
    where T : MyItem, IMyItem, new() 
{ 
    public void CreateItem() 
    { 
     T oItem = new T(); 
     oItem.Init(10); 
    } 
} 
+0

Generalmente es una mala idea, porque ahora puede haber creado objetos pero no inicializados. –

4

OMI, el mejor enfoque aquí es un método de inicialización, es decir,

interface ISomeInterface { 
    void Init(int i); 
} 
class Foo : ISomeInterface { 
    void ISomeInterface.Init(int i) { /* ... */ } 
} 
static class Program { 
    static T Create<T>(int i) where T : class, ISomeInterface, new() { 
     T t = new T(); 
     t.Init(i); 
     return t; 
    } 
    static void Main() { 
     Foo foo = Create<Foo>(123); 
    } 
} 

sin embargo, puede hacer lo que quiera con Expression (pero sin el apoyo en tiempo de compilación):

using System; 
using System.Linq.Expressions; 
class Foo { 
    public Foo(int i) { /* ... */ } 
} 
static class Program { 
    static T Create<T>(int i) { 
     return CtorCache<T>.Create(i); 
    } 
    static class CtorCache<T> { 
     static Func<int, T> ctor; 
     public static T Create(int i) { 
      if (ctor == null) ctor = CreateCtor(); 
      return ctor(i); 
     } 
     static Func<int, T> CreateCtor() { 
      var param = Expression.Parameter(typeof(int), "i"); 
      var ci = typeof(T).GetConstructor(new[] {typeof(int)}); 
      if(ci == null) throw new InvalidOperationException("No such ctor"); 
      var body = Expression.New(ci, param); 
      return Expression.Lambda<Func<int, T>>(body, param).Compile(); 
     } 
    } 
    static void Main() { 
     Foo foo = Create<Foo>(123); 
    } 
} 

Tenga en cuenta que esto almacena en caché y reutiliza el delegado para el rendimiento.

+0

¿Esto difiere significativamente del uso de 'Activator.CreateInstance()'? Si lo entiendo correctamente, ninguno tiene compatibilidad en tiempo de compilación, y ambos lanzan un error en tiempo de ejecución si el constructor esperado no existe. – Greg

+0

@Greg - sí, si lo está usando mucho (por ejemplo, una fábrica): una vez creado, el delegado se precalifica y se juntó - no más reflexión. –