Pasar argumentos a C # genérico nuevo () de tipo plantilla

Estoy intentando crear un nuevo objeto de tipo T a través de su constructor al agregarlo a la lista.

Obtengo un error de comstackción: el mensaje de error es:

‘T’: no ​​puede proporcionar argumentos al crear una instancia de una variable

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

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

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

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

Sin embargo, eso solo funcionará cuando quieras 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(..., Func del) { ... List tabListItems = new List(); foreach (ListItem listItem in listCollection) { tabListItems.Add(del(listItem)); } ... } 

Entonces puedes llamarlo así

 GetAllItems(..., l => new Foo(l)); 

en .Net 3.5 y después de que puedas usar la clase de activador:

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

Como nadie se molestó en publicar la respuesta ‘Reflexión’ (que personalmente considero que es la mejor respuesta), aquí va:

 public static string GetAllItems(...) where T : new() { ... List tabListItems = new List(); 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 las versiones anteriores de .NET.

Inicializador de objetos

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

 public static string GetAllItems(...) where T : new() { ... List tabListItems = new List(); 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, puede llamar a Activator.CreateInstance () de la siguiente manera:

 public static string GetAllItems(...) where T : new() { ... List tabListItems = new List(); 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 Activator.CreateInstance puede tener una sobrecarga de rendimiento que es posible que desee evitar si la velocidad de ejecución es una prioridad máxima y puede mantener otra opción.

Esto no funcionará en tu situación. Solo puede especificar la restricción de que tiene un constructor vacío:

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

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

 public interface ITakesAListItem { ListItem Item { set; } } 

Entonces podrías modificar tu método para que sea este:

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

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

Pregunta muy vieja, pero nueva respuesta 😉

La versión ExpressionTree : (Creo que la solución más rápida y más limpia)

Como dijo Welly Tambunan , “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(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: 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 of Expression

 .Lambda #Lambda1(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ña desventaja

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


Prueba de rendimiento simple:

 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 

Usar Expressions es +/- 8 veces más rápido que Invocar el ConstructorInfo y +/- 20 veces más rápido que usar el Activator

Debe agregar dónde T: new () para que el comstackdor sepa que T garantiza proporcionar un constructor predeterminado.

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

Si simplemente desea inicializar un campo o propiedad miembro con el parámetro constructor, en C #> = 3 puede hacerlo de manera más fácil:

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

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

Por supuesto, puede usar 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 divertido cómo la gente de M $ agrega nuevas características al lenguaje y parece que no hace un análisis completo de los efectos secundarios. Todo lo genérico es una buena evidencia de esto …

Descubrí que recibía un error “no puedo proporcionar argumentos al crear una instancia del parámetro de tipo T”, así que necesitaba hacer esto:

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

Si tiene acceso a la clase que va a utilizar, puede usar este enfoque que utilicé.

Crea una interfaz que tenga un creador alternativo:

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

Haz tus clases con un creador vacío e implementa este método:

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

Ahora usa tus métodos generics:

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

Si no tiene acceso, ajuste la clase objective:

 public class MyClass : ICreatable1Param { public WrappedClass WrappedInstance {get; private set; } public MyClass() { //do something or nothing } public void PopulateInstance (object Param) { WrappedInstance = new WrappedClass(Param); } } 

A veces uso un enfoque que se asemeja a las respuestas que usan la inyección de propiedades, 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 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 en la cadena inicialice sus propiedades únicas, y luego invoco el método Initialize () de su padre, 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 { { "BaseValue", 1 }, { "DerivedValue", 2 } }; Console.WriteLine(CreateObject(values).ToString()); Console.WriteLine(CreateObject(values).ToString()); } public T CreateObject(IDictionary 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 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 values) { base.Initialize(values); DerivedValue = values["DerivedValue"]; } public override string ToString() { return base.ToString() + ", DerivedValue = " + DerivedValue; } } 

Esto es un poco sucio, y cuando digo algo sucio, puedo querer decir repugnancia, pero suponiendo que puedes amueblar tu tipo parametrizado con un constructor vacío, entonces:

 public static T GetTInstance() 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); } 

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

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

Ahora, acepta todo, incluso los objetos que no lo tienen.