Pase un sistema instanciado. Escriba como parámetro de tipo para una clase genérica

182

El título es un poco oscuro. Lo que quiero saber es si esto es posible:

string typeName = <read type name from somwhere>;
Type myType = Type.GetType(typeName);

MyGenericClass<myType> myGenericClass = new MyGenericClass<myType>();

Obviamente, MyGenericClass se describe como:

public class MyGenericClass<T>

En este momento, el compilador se queja de que 'El tipo o espacio de nombres' myType 'no se pudo encontrar ". Tiene que haber una manera de hacerlo.

Robert C. Barth
fuente
Generics! = Plantillas. Todas las variables de tipo genérico se resuelven en tiempo de compilación y no en tiempo de ejecución. Esta es una de esas situaciones en las que el tipo 'dinámico' de 4.0 puede ser útil.
1
@ Will - ¿de qué manera? Cuando se usa con genéricos, bajo el CTP actual esencialmente terminas llamando a las versiones de <objeto> (a menos que me falte un truco ...)
Marc Gravell
@MarcGravell puede usarlo foo.Method((dynamic)myGenericClass)para el enlace del método en tiempo de ejecución, efectivamente el patrón del localizador de servicios para las sobrecargas de métodos de un tipo.
Chris Marisic
@ChrisMarisic sí, para algunos genéricos public void Method<T>(T obj): un truco que he usado más de unas pocas veces en los últimos 6 años desde ese comentario; p
Marc Gravell
@MarcGravell, ¿hay alguna forma de modificar eso para que el método lo instancia?
barlop

Respuestas:

220

No puedes hacer esto sin reflexionar. Sin embargo, usted puede hacerlo con la reflexión. Aquí hay un ejemplo completo:

using System;
using System.Reflection;

public class Generic<T>
{
    public Generic()
    {
        Console.WriteLine("T={0}", typeof(T));
    }
}

class Test
{
    static void Main()
    {
        string typeName = "System.String";
        Type typeArgument = Type.GetType(typeName);

        Type genericClass = typeof(Generic<>);
        // MakeGenericType is badly named
        Type constructedClass = genericClass.MakeGenericType(typeArgument);

        object created = Activator.CreateInstance(constructedClass);
    }
}

Nota: si su clase genérica acepta varios tipos, debe incluir las comas cuando omita los nombres de los tipos, por ejemplo:

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);
Jon Skeet
fuente
1
OK, esto es bueno, pero ¿cómo se hace para llamar a los métodos creados? ¿Más reflexión?
Robert C. Barth
77
Bueno, si puede evitar que su tipo genérico implemente una interfaz no genérica, puede enviar a esa interfaz. Como alternativa, puede escribir su propio método genérico que hace todo el trabajo que quiere hacer con el genérico, y llamar a que con la reflexión.
Jon Skeet
1
Sí, no entiendo cómo usar creado si la única información que tiene sobre el tipo devuelto está en la variable de tipo typeArgument. Me parece que tendrías que elegir esa variable, pero no sabes lo que es, así que no estoy seguro de si puedes hacerlo con reflexión. Otra pregunta si el objeto es, por ejemplo, de tipo int si lo pasa como una variable de objeto en digamos, por ejemplo, una Lista <int> ¿funcionará esto? ¿La variable creada será tratada como un int?
theringostarrs
66
@ RobertC.Barth También puede hacer que el objeto "creado" en el ejemplo escriba "dinámico" en lugar de "objeto". De esa manera, puede invocar métodos y la evaluación se aplazará hasta el tiempo de ejecución.
McGarnagle
44
@balanza: Usas MakeGenericMethod.
Jon Skeet
14

Lamentablemente no, no hay. Los argumentos genéricos deben poder resolverse en tiempo de compilación como 1) un tipo válido u 2) otro parámetro genérico. No hay forma de crear instancias genéricas basadas en valores de tiempo de ejecución sin el gran martillo de usar la reflexión.

JaredPar
fuente
2

Algunas formas adicionales de ejecutar con código de tijera. Supongamos que tienes una clase similar a

public class Encoder() {
public void Markdown(IEnumerable<FooContent> contents) { do magic }
public void Markdown(IEnumerable<BarContent> contents) { do magic2 }
}

Supongamos que en tiempo de ejecución tienes un FooContent

Si pudieras enlazar en tiempo de compilación, querrías

var fooContents = new List<FooContent>(fooContent)
new Encoder().Markdown(fooContents)

Sin embargo, no puede hacer esto en tiempo de ejecución. Para hacer esto en tiempo de ejecución, haría lo siguiente:

var listType = typeof(List<>).MakeGenericType(myType);
var dynamicList = Activator.CreateInstance(listType);
((IList)dynamicList).Add(fooContent);

Invocar dinámicamente Markdown(IEnumerable<FooContent> contents)

new Encoder().Markdown( (dynamic) dynamicList)

Tenga en cuenta el uso de dynamicen la llamada al método. En el tiempo de ejecución dynamicListserá List<FooContent>(además de serlo IEnumerable<FooContent>), ya que incluso el uso de la dinámica todavía está enraizado en un lenguaje fuertemente tipado, el enlazador de tiempo de ejecución seleccionará el Markdownmétodo apropiado . Si no hay coincidencias de tipo exactas, buscará un método de parámetro de objeto y si ninguna coincide, se generará una excepción de carpeta de tiempo de ejecución alertando que ningún método coincide.

El inconveniente obvio de este enfoque es una gran pérdida de seguridad de tipo en tiempo de compilación. Sin embargo, el código a lo largo de estas líneas le permitirá operar en un sentido muy dinámico que en tiempo de ejecución todavía está completamente escrito como espera que sea.

Chris Marisic
fuente
2

Mis requisitos eran ligeramente diferentes, pero espero que ayuden a alguien. Necesitaba leer el tipo de una configuración e instanciar el tipo genérico dinámicamente.

namespace GenericTest
{
    public class Item
    {
    }
}

namespace GenericTest
{
    public class GenericClass<T>
    {
    }
}

Finalmente, así es como lo llamas. Defina el tipo con un backtick .

var t = Type.GetType("GenericTest.GenericClass`1[[GenericTest.Item, GenericTest]], GenericTest");
var a = Activator.CreateInstance(t);
Master P
fuente
0

Si sabe qué tipos se pasarán, puede hacerlo sin reflexionar. Una declaración de cambio funcionaría. Obviamente, esto solo funcionaría en un número limitado de casos, pero será mucho más rápido que la reflexión.

public class Type1 { }

public class Type2 { }

public class Generic<T> { }

public class Program
{
    public static void Main()
    {
        var typeName = nameof(Type1);

        switch (typeName)
        {
            case nameof(Type1):
                var type1 = new Generic<Type1>();
                // do something
                break;
            case nameof(Type2):
                var type2 = new Generic<Type2>();
                // do something
                break;
        }
    }
}
Todd Skelton
fuente
esto se pone feo rápido una vez que comienzas a lidiar con cientos de clases.
michael g
0

En este fragmento, quiero mostrar cómo crear y usar una lista creada dinámicamente. Por ejemplo, estoy agregando a la lista dinámica aquí.

void AddValue<T>(object targetList, T valueToAdd)
{
    var addMethod = targetList.GetType().GetMethod("Add");
    addMethod.Invoke(targetList, new[] { valueToAdd } as object[]);
}

var listType = typeof(List<>).MakeGenericType(new[] { dynamicType }); // dynamicType is the type you want
var list = Activator.CreateInstance(listType);

AddValue(list, 5);

Del mismo modo, puede invocar cualquier otro método en la lista.

EGN
fuente