2009-05-08 13 views
344

Estoy tratando de crear un nuevo objeto de tipo T a través de su constructor al agregarlo a la lista.Pasando argumentos a C# genérico nuevo() del tipo de plantilla

que estoy recibiendo un error de compilación: El mensaje de error es:

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

Pero mis clases tienen un argumento del constructor! ¿Cómo puedo hacer que esto funcione?

public static string GetAllItems<T>(...) where T : new() 
{ 
    ... 
    List<T> tabListItems = new List<T>(); 
    foreach (ListItem listItem in listCollection) 
    { 
     tabListItems.Add(new T(listItem)); // error here. 
    } 
    ... 
} 
+2

posible duplicado de [Crear instancia de tipo genérico?] (Http://stackoverflow.com/questions/731452/create-instanc e-of-generic-type) – nawfal

+2

Propuesta para obtener esta funcionalidad en el idioma: https://github.com/dotnet/roslyn/issues/2206 –

+0

En la documentación de Microsoft, consulte [Error del compilador CS0417] (https: // docs .microsoft.com/es-us/dotnet/csharp/language-reference/compiler-messages/cs0417). – DavidRR

Respuesta

354

Para crear una instancia de un tipo genérico en una función, debe restringirla con la marca "nueva".

public static string GetAllItems<T>(...) where T : new() 

Sin embargo, eso solo funcionará cuando desee llamar al constructor que no tiene parámetros. No es el caso aquí. En su lugar, deberá proporcionar otro parámetro que permita la creación de objetos en función de los parámetros. Lo más fácil es una función.

public static string GetAllItems<T>(..., Func<ListItem,T> del) { 
    ... 
    List<T> tabListItems = new List<T>(); 
    foreach (ListItem listItem in listCollection) 
    { 
    tabListItems.Add(del(listItem)); 
    } 
    ... 
} 

entonces le puede llamar como tal

GetAllItems<Foo>(..., l => new Foo(l)); 
+0

¿Cómo funcionaría esto cuando se llame internamente desde una clase genérica? He publicado mi código en una respuesta a continuación. No conozco la clase concreta internamente, ya que es una clase genérica. ¿Hay alguna manera de evitar esto? No quiero usar la otra sugerencia de utilizar la sintaxis del inicializador de propiedades ya que omitirá la lógica que tengo en el constructor – ChrisCa

+0

agregué mi código a otra pregunta http://stackoverflow.com/questions/1682310/c-generics- problem-newing-up-the-generic-type-with-parameters-in-the-construct – ChrisCa

+10

Esta es actualmente una de las limitaciones más molestas de C#. Me gustaría que mis clases sean inmutables: tener instaladores privados haría que la clase fuera imposible por los efectos secundarios. También me gusta usar ese Func y lambda, pero sé que todavía es un problema en el mundo de los negocios ya que generalmente los programadores aún no conocen lambdas y esto hace que tu clase sea más difícil de entender. –

8

Debes agregar dónde T: new() para que el compilador sepa que T está garantizado para proporcionar un constructor predeterminado.

public static string GetAllItems<T>(...) where T: new() 
+1

ACTUALIZACIÓN: El mensaje de error correcto es: 'T': no ​​puede proporcionar argumentos al crear una instancia de una variable –

+0

Eso es porque no está utilizando un constructor en blanco, le está pasando un argumento de objeto. No hay manera de que pueda manejar eso sin especificar que el Tipo genérico tenga un nuevo parámetro (objeto). – Min

+0

Luego deberá: 1. Utilizar el reflejo 2. Pasar el parámetro a un método de inicialización en lugar del constructor, donde el método de inicialización pertenece a una interfaz que su tipo implementa y que se incluye en el : ... declaración. La opción 1 es el impacto más bajo para el resto de su código, pero la opción 2 proporciona comprobación de tiempo de compilación. – Richard

-3

Creo que tiene que restringir T con una instrucción where para permitir solo objetos con un nuevo constructor.

Rigth ahora acepta cualquier cosa, incluidos los objetos que no lo tienen.

+1

Es posible que desee cambiar esta respuesta porque esto se editó en la pregunta después de su respuesta, lo que deja esta respuesta fuera de contexto. – shuttle87

16

Esto no funcionará en su situación. Sólo puede especificar la restricción que tiene un constructor vacío:

public static string GetAllItems<T>(...) where T: new() 

Lo que podría hacer es usar la inyección de la propiedad mediante la definición de esta interfaz:

public interface ITakesAListItem 
{ 
    ListItem Item { set; } 
} 

entonces se podría alterar su método es el siguiente:

public static string GetAllItems<T>(...) where T : ITakesAListItem, new() 
{ 
    ... 
    List<T> tabListItems = new List<T>(); 
    foreach (ListItem listItem in listCollection) 
    { 
     tabListItems.Add(new T() { Item = listItem }); 
    } 
    ... 
} 

La otra alternativa es el método Func descrito por JaredPar.

+0

esto omitiría cualquier lógica que está en el constructor que toma los argumentos, ¿verdad? Me gustaría hacer algo como el enfoque de Jared pero estoy llamando al método internamente dentro de la clase, así que no sé cuál es el tipo concreto ... hmmm – ChrisCa

+3

Correcto, esto llama la lógica del constructor predeterminado T(), luego simplemente establece la propiedad "Artículo". Si intenta invocar la lógica de un constructor no predeterminado, esto no lo ayudará. –

7

Si simplemente desea inicializar un campo miembro o propiedad con el parámetro constructor, en C#> = 3 que puede hacer que sea muy fácil:

public static string GetAllItems<T>(...) where T : InterfaceOrBaseClass, new() 
{ 
    ... 
    List<T> tabListItems = new List<T>(); 
    foreach (ListItem listItem in listCollection) 
    { 
     tabListItems.Add(new T{ BaseMemberItem = listItem }); // No error, BaseMemberItem owns to InterfaceOrBaseClass. 
    } 
    ... 
} 

Esto es lo mismo que Garry Shutler dijo, pero yo Me gustaría poner una nota adicional.

Por supuesto, puede utilizar un truco de propiedad para hacer más cosas que simplemente establecer un valor de campo. Una propiedad "set()" puede desencadenar cualquier procesamiento necesario para configurar sus campos relacionados y cualquier otra necesidad para el objeto en sí, incluida una comprobación para ver si se va a realizar una inicialización completa antes de usar el objeto, simulando una construcción completa (Sí, es una solución fea, pero supera la nueva limitación de M $).

No puedo asegurar si es un agujero planificado o un efecto secundario accidental, pero funciona.

Es muy gracioso cómo M $ personas agrega nuevas características al lenguaje y parece no hacer un análisis completo de los efectos secundarios. toda la cosa genérica es una buena prueba de ello ...

+1

Ambas restricciones son necesarias. InterfaceOrBaseClass hace que el compilador tenga conocimiento del campo/propiedad BaseMemberItem. Si el "nuevo()" restricción se ha comentado, se disparará el error: error No se puede crear una instancia del tipo de variable 'T', ya que no tiene la nueva restricción() – fljx

+0

Una situación que encontrado no era exactamente como la pregunta que se hace aquí, sin embargo, esta respuesta me llevó a donde tenía que ir y parece que funciona muy bien. – RubyHaus

+4

Cada vez que alguien menciona a Microsoft como "M $", una pequeña parte de mi alma sufre. –

49

Dado que nadie se molestó en publicar la respuesta 'reflexión' (que yo personalmente creo que es la mejor respuesta), aquí va:

public static string GetAllItems<T>(...) where T : new() 
{ 
    ... 
    List<T> tabListItems = new List<T>(); 
    foreach (ListItem listItem in listCollection) 
    { 
     Type classType = typeof(T); 
     ConstructorInfo classConstructor = classType.GetConstructor(new Type[] { listItem.GetType() }); 
     T classInstance = (T)classConstructor.Invoke(new object[] { listItem }); 

     tabListItems.Add(classInstance); 
    } 
    ... 
} 

Editar : Esta respuesta está en desuso debido al Activator.CreateInstance de .NET 3.5, sin embargo, sigue siendo útil en versiones anteriores de .NET.

+3

¿El reflejo no conlleva una penalización de rendimiento significativa? –

+11

"Significativo" dependería de la aplicación. En la mayoría de los casos, probablemente no. –

+0

Según tengo entendido, la mayor parte del rendimiento alcanzado es la adquisición de ConstructorInfo en primer lugar. No confíe en mi palabra sin perfilarlo. Si ese es el caso, simplemente almacenar el ConstructorInfo para su reutilización posterior podría aliviar el impacto de rendimiento de instancias repetidas a través de la reflexión. – Kelsie

0

Esto es un poco sucio, y cuando digo especie de sucia que puede significar repugnante, pero suponiendo que se puede amueblar su tipo parametrizado con un constructor vacío, entonces:

public static T GetTInstance<T>() where T: new() 
{ 
    var constructorTypeSignature = new Type[] {typeof (object)}; 
    var constructorParameters = new object[] {"Create a T"}; 
    return (T) new T().GetType().GetConstructor(constructorTypeSignature).Invoke(constructorParameters); 
} 

Permitirá efectivamente a construir un objeto de un tipo parametrizado con un argumento. En este caso, supongo que el constructor que quiero tiene un único argumento del tipo object. Creamos una instancia ficticia de T usando el constructor vacío permitido de restricción y luego usamos el reflejo para obtener uno de sus otros constructores.

+1

Ya respondió aquí http://stackoverflow.com/a/3054835/661933 por James Jones – nawfal

283

en .Net 3.5 y después de que usted podría utilizar la clase de activador:

(T)Activator.CreateInstance(typeof(T), args) 
+1

también podríamos usar el árbol de expresiones para construir el objeto –

+3

¿Qué es args? un objeto[]? –

+3

Sí, args es un objeto [] donde especifica los valores que se proporcionarán al constructor de la T: "nuevo objeto [] {par1, par2}" – TechNyquist

26

inicializador de objeto

Si su constructor con el parámetro no está haciendo nada, además de establecer una propiedad, puede haga esto en C# 3 o mejor usando un object initializer en lugar de llamar a un constructor (que es imposible, como se ha mencionado):

public static string GetAllItems<T>(...) where T : new() 
{ 
    ... 
    List<T> tabListItems = new List<T>(); 
    foreach (ListItem listItem in listCollection) 
    { 
     tabListItems.Add(new T() { YourPropertyName = listItem }); // Now using object initializer 
    } 
    ... 
} 

Usando esto, siempre puedes poner cualquier lógica de constructor en el constructor predeterminado (vacío), también.

Activator.CreateInstance()

Alternativamente, se podría llamar Activator.CreateInstance() así:

public static string GetAllItems<T>(...) where T : new() 
{ 
    ... 
    List<T> tabListItems = new List<T>(); 
    foreach (ListItem listItem in listCollection) 
    { 
     object[] args = new object[] { listItem }; 
     tabListItems.Add((T)Activator.CreateInstance(typeof(T), args)); // Now using Activator.CreateInstance 
    } 
    ... 
} 

Tenga en cuenta que puede tener algún Activator.CreateInstance performance overhead que es posible que desee evitar si la velocidad de ejecución es una prioridad máxima y otra opción es mantenible para usted.

+0

esto impide que 'T' proteja sus invariantes (dado que' T' tiene> 0 dependencias o valores requeridos, ahora puede crear instancias de 'T' que están en un estado no válido/inutilizable, a menos que' T' sea algo simple como un DTO o modelo de vista, yo diría que evite esto. – kai

5

me di cuenta que estaba recibiendo un error "no puede proporcionar argumentos al crear una instancia de parámetro de tipo T", por lo que tenía que hacer esto:

var x = Activator.CreateInstance(typeof(T), args) as T; 
10

muy vieja pregunta, pero nueva respuesta ;-)

La versión ExpressionTree: (creo que la solución más limpia y fastests)

Como Welly Tambunan dijo, "también podríamos usar el árbol de expresiones para construir el objeto"

Esto generará un 'constructor' (función) para el tipo/parámetros dados. Devuelve un delegado y acepta los tipos de parámetros como una matriz de objetos.

Aquí está:

// this delegate is just, so you don't have to pass an object array. _(params)_ 
public delegate object ConstructorDelegate(params object[] args); 

public static ConstructorDelegate CreateConstructor(Type type, params Type[] parameters) 
{ 
    // Get the constructor info for these parameters 
    var constructorInfo = type.GetConstructor(parameters); 

    // define a object[] parameter 
    var paramExpr = Expression.Parameter(typeof(Object[])); 

    // To feed the constructor with the right parameters, we need to generate an array 
    // of parameters that will be read from the initialize object array argument. 
    var constructorParameters = parameters.Select((paramType, index) => 
     // convert the object[index] to the right constructor parameter type. 
     Expression.Convert(
      // read a value from the object[index] 
      Expression.ArrayAccess(
       paramExpr, 
       Expression.Constant(index)), 
      paramType)).ToArray(); 

    // just call the constructor. 
    var body = Expression.New(constructorInfo, constructorParameters); 

    var constructor = Expression.Lambda<ConstructorDelegate>(body, paramExpr); 
    return constructor.Compile(); 
} 

Ejemplo MyClass:

public class MyClass 
{ 
    public int TestInt { get; private set; } 
    public string TestString { get; private set; } 

    public MyClass(int testInt, string testString) 
    { 
     TestInt = testInt; 
     TestString = testString; 
    } 
} 

Uso:

// you should cache this 'constructor' 
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string)); 

// Call the `myConstructor` fucntion to create a new instance. 
var myObject = myConstructor(10, "test message"); 

enter image description here


Otro ejemplo: hacer pasar los tipos como una matriz

var type = typeof(MyClass); 
var args = new Type[] { typeof(int), typeof(string) }; 

// you should cache this 'constructor' 
var myConstructor = CreateConstructor(type, args); 

// Call the `myConstructor` fucntion to create a new instance. 
var myObject = myConstructor(10, "test message"); 

DebugView de Expresión

.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) { 
    .New TestExpressionConstructor.MainWindow+MyClass(
     (System.Int32)$var1[0], 
     (System.String)$var1[1]) 
} 

Esto es equivalente al código que se genera:

public object myConstructor(object[] var1) 
{ 
    return new MyClass(
     (System.Int32)var1[0], 
     (System.String)var1[1]); 
} 

pequeño inconveniente

Todos valuetypes parámetros están en caja cuando se pasan como una matriz de objetos.


simple prueba de rendimiento:

private void TestActivator() 
{ 
    Stopwatch sw = Stopwatch.StartNew(); 
    for (int i = 0; i < 1024 * 1024 * 10; i++) 
    { 
     var myObject = Activator.CreateInstance(typeof(MyClass), 10, "test message"); 
    } 
    sw.Stop(); 
    Trace.WriteLine("Activator: " + sw.Elapsed); 
} 

private void TestReflection() 
{ 
    var constructorInfo = typeof(MyClass).GetConstructor(new[] { typeof(int), typeof(string) }); 

    Stopwatch sw = Stopwatch.StartNew(); 
    for (int i = 0; i < 1024 * 1024 * 10; i++) 
    { 
     var myObject = constructorInfo.Invoke(new object[] { 10, "test message" }); 
    } 

    sw.Stop(); 
    Trace.WriteLine("Reflection: " + sw.Elapsed); 
} 

private void TestExpression() 
{ 
    var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string)); 

    Stopwatch sw = Stopwatch.StartNew(); 

    for (int i = 0; i < 1024 * 1024 * 10; i++) 
    { 
     var myObject = myConstructor(10, "test message"); 
    } 

    sw.Stop(); 
    Trace.WriteLine("Expression: " + sw.Elapsed); 
} 

TestActivator(); 
TestReflection(); 
TestExpression(); 

Resultados:

Activator: 00:00:13.8210732 
Reflection: 00:00:05.2986945 
Expression: 00:00:00.6681696 

Usando Expressions es de +/- 8 veces más rápido que Invocando la ConstructorInfo y +/- 20 veces más rápido que utilizando el Activator

+0

¿Tiene alguna idea de qué hacer si desea construir MyClass con el constructor público MyClass (datos T). En este caso, Expression.Convert arroja una excepción y si uso la clase base de restricción genérica para convertir a, entonces Expression.New arroja porque la información del constructor es para un tipo genérico – Mason

1

A veces utilizo un enfoque que se asemeja a las respuestas que usan la inyección de propiedad, pero mantiene el código más limpio. En lugar de tener una clase base/interfaz con un conjunto de propiedades, solo contiene un método (virtual) Initialize() que actúa como un "constructor de personas pobres". Luego puede dejar que cada clase maneje su propia inicialización como lo haría un constructor, lo que también agrega una manera conveniente de manejar las cadenas de herencia.

Si a menudo me encuentro en situaciones en las que quiero que cada clase de la cadena inicialice sus propiedades únicas, y luego invoco el método Initialize() original que a su vez inicializa las propiedades únicas de los padres, etc. Esto es especialmente útil cuando se tienen clases diferentes, pero con una jerarquía similar, por ejemplo, objetos comerciales mapeados a/desde DTO: s.

Ejemplo que utiliza un diccionario común para la inicialización:

void Main() 
{ 
    var values = new Dictionary<string, int> { { "BaseValue", 1 }, { "DerivedValue", 2 } }; 

    Console.WriteLine(CreateObject<Base>(values).ToString()); 

    Console.WriteLine(CreateObject<Derived>(values).ToString()); 
} 

public T CreateObject<T>(IDictionary<string, int> values) 
    where T : Base, new() 
{ 
    var obj = new T(); 
    obj.Initialize(values); 
    return obj; 
} 

public class Base 
{ 
    public int BaseValue { get; set; } 

    public virtual void Initialize(IDictionary<string, int> values) 
    { 
     BaseValue = values["BaseValue"]; 
    } 

    public override string ToString() 
    { 
     return "BaseValue = " + BaseValue; 
    } 
} 

public class Derived : Base 
{ 
    public int DerivedValue { get; set; } 

    public override void Initialize(IDictionary<string, int> values) 
    { 
     base.Initialize(values); 
     DerivedValue = values["DerivedValue"]; 
    } 

    public override string ToString() 
    {  
     return base.ToString() + ", DerivedValue = " + DerivedValue; 
    } 
} 
0

Si tienen acceso a la clase que va a utilizar, puede utilizar este método que he utilizado.

crear una interfaz que tiene un creador alternativa:

public interface ICreatable1Param 
{ 
    void PopulateInstance(object Param); 
} 

Haga sus clases con un creador de vacío y poner en práctica este método:

public class MyClass : ICreatable1Param 
{ 
    public MyClass() { //do something or nothing } 
    public void PopulateInstance (object Param) 
    { 
     //populate the class here 
    } 
} 

Ahora usa sus métodos genéricos:

public void MyMethod<T>(...) where T : ICreatable1Param, new() 
{ 
    //do stuff 
    T newT = new T(); 
    T.PopulateInstance(Param); 
} 
Cuestiones relacionadas