Przekaż instancyjny System.Type jako parametr typu dla klasy ogólnej

182

Tytuł jest niejasny. Chcę wiedzieć, czy jest to możliwe:

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

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

Oczywiście MyGenericClass jest opisany jako:

public class MyGenericClass<T>

W tej chwili kompilator narzeka, że ​​„Nie można znaleźć typu lub przestrzeni nazw„ myType ”.” Musi być na to sposób.

Robert C. Barth
źródło
Ogólne! = Szablony. Wszystkie zmienne typu ogólnego są rozstrzygane w czasie kompilacji, a nie w czasie wykonywania. Jest to jedna z tych sytuacji, w których „dynamiczny” typ 4.0 może być przydatny.
1
@Will - w jaki sposób? W przypadku użycia z lekami generycznymi, w ramach obecnego CTP w zasadzie wywołujesz wersje <object> (chyba że brakuje mi trika ...)
Marc Gravell
@MarcGravell można użyć foo.Method((dynamic)myGenericClass)do powiązania metod w czasie wykonywania, skutecznie wzorzec lokalizatora usługi dla przeciążeń metody typu.
Chris Marisic
@ChrisMarisic tak, dla niektórych ogólnych public void Method<T>(T obj)- sztuczka, z której korzystałem więcej niż kilka razy w ciągu ostatnich 6 lat od tego komentarza; p
Marc Gravell
@MarcGravell czy istnieje sposób, aby to zmienić, aby metoda utworzyła instancję?
barlop

Odpowiedzi:

220

Nie możesz tego zrobić bez refleksji. Jednakże, można zrobić to z refleksji. Oto kompletny przykład:

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

Uwaga: jeśli twoja klasa ogólna akceptuje wiele typów, musisz pominąć przecinki, pomijając nazwy typów, na przykład:

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);
Jon Skeet
źródło
1
OK, to dobrze, ale jak przejść do wywoływania metod na utworzonych? Więcej refleksji?
Robert C. Barth,
7
Cóż, jeśli uda ci się zaimplementować ogólny typ implementacji interfejsu innego niż ogólny, możesz przerzucić na ten interfejs. Alternatywnie, możesz napisać własną metodę ogólną, która wykonuje całą pracę, którą chcesz zrobić z ogólną, i nazwać to refleksją.
Jon Skeet,
1
Tak, nie zastanawiam się, jak korzystać z utworzonego, jeśli jedyne informacje na temat zwróconego typu znajdują się w zmiennej typeArgument? Wydaje mi się, że musiałbyś rzucić na tę zmienną, ale nie wiesz, co to jest, więc nie jestem pewien, czy możesz to zrobić za pomocą refleksji. Kolejne pytanie, czy obiekt jest na przykład typu int, jeśli przekażesz go jako zmienną obiektową do powiedzmy na przykład List <int> czy to działa? czy utworzona zmienna będzie traktowana jako int?
theringostarrs
6
@ RobertC.Barth Można również utworzyć „utworzony” obiekt w przykładzie typu „dynamiczny” zamiast „obiekt”. W ten sposób możesz wywoływać na nim metody, a ocena zostanie odroczona do czasu wykonania.
McGarnagle
4
@balanza: Używasz MakeGenericMethod.
Jon Skeet
14

Niestety nie, nie ma. Argumenty ogólne muszą być rozwiązywalne w czasie kompilacji jako 1) prawidłowy typ lub 2) inny parametr ogólny. Nie ma sposobu na utworzenie ogólnych instancji w oparciu o wartości środowiska wykonawczego bez dużego młota przy użyciu odbicia.

JaredPar
źródło
2

Dodatkowe informacje na temat uruchamiania przy użyciu kodu nożyczek. Załóżmy, że masz klasę podobną do

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

Załóżmy, że w czasie wykonywania masz FooContent

Gdybyś mógł powiązać w czasie kompilacji, chciałbyś

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

Jednak nie można tego zrobić w czasie wykonywania. Aby to zrobić w środowisku wykonawczym, wykonaj następujące czynności:

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

Aby dynamicznie wywoływać Markdown(IEnumerable<FooContent> contents)

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

Zwróć uwagę na użycie dynamicw wywołaniu metody. W czasie wykonywania dynamicListbędzie List<FooContent>(dodatkowo również IEnumerable<FooContent>), ponieważ nawet użycie dynamiki jest nadal zakorzenione w silnie typowanym języku, spoiwo w czasie wykonywania wybierze odpowiednią Markdownmetodę. Jeśli nie ma dokładnych dopasowań typu, będzie szukał metody parametru obiektowego, a jeśli nie będzie pasował żaden wyjątek spoiwa środowiska wykonawczego, pojawi się ostrzeżenie, że żadna metoda nie pasuje.

Oczywistą wadą tego podejścia jest ogromna utrata bezpieczeństwa typu w czasie kompilacji. Niemniej jednak kod zgodny z tymi liniami pozwoli ci działać w bardzo dynamiczny sposób, że w środowisku wykonawczym jest nadal w pełni wpisany, tak jak się spodziewasz.

Chris Marisic
źródło
2

Moje wymagania były nieco inne, ale mam nadzieję, że komuś pomogą. Musiałem odczytać typ z konfiguracji i dynamicznie utworzyć instancję typu ogólnego.

namespace GenericTest
{
    public class Item
    {
    }
}

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

Wreszcie, jak to nazywasz. Zdefiniuj typ za pomocą przycisku wstecz .

var t = Type.GetType("GenericTest.GenericClass`1[[GenericTest.Item, GenericTest]], GenericTest");
var a = Activator.CreateInstance(t);
Mistrz P.
źródło
0

Jeśli wiesz, jakie typy zostaną zaliczone, możesz to zrobić bez refleksji. Instrukcja switch działałaby. Oczywiście działałoby to tylko w ograniczonej liczbie przypadków, ale będzie znacznie szybsze niż refleksja.

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
źródło
robi się brzydko szybko, gdy zaczynasz radzić sobie z setkami klas.
michael g
0

W tym fragmencie chcę pokazać, jak utworzyć i używać dynamicznie tworzonej listy. Na przykład dodaję tutaj listę dynamiczną.

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

Podobnie możesz wywołać dowolną inną metodę z listy.

EGN
źródło