Passe um System.Type instanciado como um parâmetro de tipo para uma classe genérica

182

O título é meio obscuro. O que eu quero saber é se isso é possível:

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

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

Obviamente, MyGenericClass é descrito como:

public class MyGenericClass<T>

No momento, o compilador reclama que 'O tipo ou espaço de nome' myType 'não foi encontrado. "É preciso haver uma maneira de fazer isso.

Robert C. Barth
fonte
Genéricos! = Modelos. Todas as variáveis ​​de tipo genérico são resolvidas no tempo de compilação e não no tempo de execução. Essa é uma daquelas situações em que o tipo 'dinâmico' de 4.0 pode ser útil.
1
@ Will - de que maneira? Quando usado com os genéricos, sob a CTP atual que você essencialmente acabam chamando <object> versões (a menos que eu estou faltando um truque ...)
Marc Gravell
@ MarcGravell você pode usar foo.Method((dynamic)myGenericClass)para a ligação do método em tempo de execução, efetivamente o padrão do localizador de serviço para sobrecargas de método de um tipo.
21414 Chris Marisic
@ ChrishrisMarisic sim, para alguns genéricos public void Method<T>(T obj)- um truque que eu usei mais de algumas vezes nos últimos 6 anos desde esse comentário; p
Marc Gravell
@ MarcGravell, existe uma maneira de alterar isso para que o método instancie isso?
Barlop

Respostas:

220

Você não pode fazer isso sem reflexão. No entanto, você pode fazer isso com reflexão. Aqui está um exemplo 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: se sua classe genérica aceitar vários tipos, você deverá incluir vírgulas ao omitir os nomes dos tipos, por exemplo:

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);
Jon Skeet
fonte
1
OK, isso é bom, mas como se chama métodos na criação? Mais reflexão?
Robert C. Barth
7
Bem, se você conseguir fazer com que seu tipo genérico implemente uma interface não genérica, poderá transmitir para essa interface. Como alternativa, você pode escrever seu próprio método genérico, que faz todo o trabalho que você deseja fazer com o genérico, e chamar isso de reflexão.
Jon Skeet
1
Sim, eu não entendo como usar criado se a única informação que você tem sobre o tipo retornado está na variável typeArgument type? Parece-me que você precisaria converter essa variável, mas você não sabe o que é, então não tenho certeza se você pode fazer isso com reflexão. Outra pergunta, se o objeto for, por exemplo, do tipo int, se você o passar como variável de objeto para, por exemplo, uma lista <int>, isso funcionará? a variável criada será tratada como um int?
theringostarrs
6
@ RobertC.Barth Você também pode criar o objeto "criado" no tipo de exemplo "dinâmico" em vez de "objeto". Dessa forma, você pode chamar métodos e a avaliação será adiada até o tempo de execução.
McGarnagle
4
@balanza: Você usa MakeGenericMethod.
Jon Skeet
14

Infelizmente não, não existe. Os argumentos genéricos devem ser resolvidos no momento da compilação como 1) um tipo válido ou 2) outro parâmetro genérico. Não há como criar instâncias genéricas baseadas em valores de tempo de execução sem o grande martelo de usar a reflexão.

JaredPar
fonte
2

Algumas dicas adicionais sobre como executar com o código de tesoura. Suponha que você tenha uma classe semelhante a

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

Suponha que, em tempo de execução, você tenha um FooContent

Se você fosse capaz de ligar em tempo de compilação, desejaria

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

No entanto, você não pode fazer isso em tempo de execução. Para fazer isso em tempo de execução, faça o seguinte:

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

Para chamar dinamicamente Markdown(IEnumerable<FooContent> contents)

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

Observe o uso de dynamicna chamada de método. No tempo de execução, dynamicListhaverá List<FooContent>(adicionalmente também sendo IEnumerable<FooContent>), pois mesmo o uso de dinâmico ainda está enraizado em uma linguagem fortemente tipada, o fichário de tempo de execução selecionará o Markdownmétodo apropriado . Se não houver correspondências exatas de tipo, ele procurará um método de parâmetro de objeto e se nenhuma delas corresponder a uma exceção do fichário de tempo de execução será gerada, alertando que nenhum método corresponde.

A desvantagem óbvia dessa abordagem é uma enorme perda de segurança de tipo em tempo de compilação. No entanto, o código nesse sentido permitirá operar em um sentido muito dinâmico, que no tempo de execução ainda é totalmente digitado conforme o esperado.

Chris Marisic
fonte
2

Meus requisitos eram um pouco diferentes, mas espero ajudar alguém. Eu precisava ler o tipo de uma configuração e instanciar o tipo genérico dinamicamente.

namespace GenericTest
{
    public class Item
    {
    }
}

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

Finalmente, aqui está como você chama. Defina o tipo com um backtick .

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

Se você souber quais tipos serão passados, poderá fazer isso sem reflexão. Uma declaração de chave funcionaria. Obviamente, isso funcionaria apenas em um número limitado de casos, mas será muito mais rápido que a reflexão.

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
fonte
isso fica feio rapidamente quando você começa a lidar com centenas de classes.
Michael G
0

Neste snippet, quero mostrar como criar e usar uma lista criada dinamicamente. Por exemplo, estou adicionando à lista dinâmica aqui.

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

Da mesma forma, você pode invocar qualquer outro método na lista.

EGN
fonte