c# - Pasar argumentos a C # new genérico () de tipo de plantilla



6 Answers

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

(T)Activator.CreateInstance(typeof(T), args)
c# .net generics new-operator

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

Recibo 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 de 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 hace nada más que establecer una propiedad, puede hacerlo 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 puede poner cualquier lógica de constructor en el constructor predeterminado (vacío), también.

Activator.CreateInstance ()

Alternativamente, puedes llamar a 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 Activator.CreateInstance puede tener una sobrecarga de rendimiento que puede evitar si la velocidad de ejecución es una prioridad máxima y otra opción es mantenible para usted.




Muy antigua pregunta, pero nueva respuesta ;-)

La versión de ExpressionTree : (Creo que la solución más rápida y 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 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 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 a ConstructorInfo y +/- 20 veces más rápido que usar el Activator




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

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 los campos relacionados y cualquier otra necesidad para el objeto en sí, incluyendo una verificación para ver si se debe realizar una inicialización completa antes de que se use el objeto, simulando una construcción completa ( sí, es una solución fea, pero supera la nueva limitación de M $ ().

No puedo estar seguro de que sea un hoyo planificado o un efecto secundario accidental, pero funciona.

Es muy divertido cómo M $ people agrega nuevas funciones al lenguaje y parece que no realiza un análisis completo de los efectos secundarios. Todo lo genérico es una buena evidencia de esto ...




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

Crea una interfaz que tenga un creador alternativo:

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

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

Si no tiene acceso, envuelva la clase de destino:

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



Esto es una especie de mala suerte, y cuando digo una especie de mala suerte me refiero a rebelarme, pero suponiendo que pueda proporcionar 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);
}

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




Related