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



Answers

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

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

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

Obtengo un error de compilació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<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T(listItem)); // error here.
   } 
   ...
}



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<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, puede llamar a Activator.CreateInstance() manera:

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 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.




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<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;
    }
}



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<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 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 ...




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<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");

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<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ñ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




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 genéricos:

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



Links