Übergeben Sie ein instanziiertes System.Type als Typparameter für eine generische Klasse

181

Der Titel ist irgendwie dunkel. Ich möchte wissen, ob dies möglich ist:

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

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

Offensichtlich wird MyGenericClass wie folgt beschrieben:

public class MyGenericClass<T>

Im Moment beschwert sich der Compiler, dass 'Der Typ oder Namespace' myType 'nicht gefunden werden konnte. "Es muss einen Weg geben, dies zu tun.

Robert C. Barth
quelle
Generika! = Vorlagen. Alle generischen Typvariablen werden zur Kompilierungszeit und nicht zur Laufzeit aufgelöst. Dies ist eine dieser Situationen, in denen der 'dynamische' Typ 4.0 nützlich sein kann.
1
@ Will - auf welche Weise? Bei Verwendung mit Generika rufen Sie unter dem aktuellen CTP im Wesentlichen die <Objekt> -Versionen auf (es sei denn, mir fehlt ein Trick ...)
Marc Gravell
@MarcGravell, das Sie foo.Method((dynamic)myGenericClass)für die Laufzeitmethodenbindung verwenden können, effektiv das Service Locator-Muster für die Methodenüberladungen eines Typs.
Chris Marisic
@ ChrisMarisic ja, für einige Generika public void Method<T>(T obj)- ein Trick, den ich in den letzten 6 Jahren seit diesem Kommentar mehr als ein paar Mal angewendet habe; p
Marc Gravell
@MarcGravell gibt es eine Möglichkeit, dies so zu ändern, dass die Methode es instanziiert?
Barlop

Antworten:

219

Sie können dies nicht ohne Reflexion tun. Sie können dies jedoch mit Reflexion tun. Hier ist ein vollständiges Beispiel:

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

Hinweis: Wenn Ihre generische Klasse mehrere Typen akzeptiert, müssen Sie die Kommas einfügen, wenn Sie die Typnamen weglassen. Beispiel:

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);
Jon Skeet
quelle
1
OK, das ist gut, aber wie geht man vor, um Methoden auf erstellt aufzurufen? Mehr Nachdenken?
Robert C. Barth
7
Wenn Sie es schaffen, Ihren generischen Typ dazu zu bringen, eine nicht generische Schnittstelle zu implementieren, können Sie diese Schnittstelle umwandeln. Alternativ können Sie Ihre eigene generische Methode schreiben, die die gesamte Arbeit erledigt, die Sie mit der generischen machen möchten, und diese mit Reflexion aufrufen .
Jon Skeet
1
Ja, ich verstehe nicht, wie man erstellt verwendet, wenn die einzige Information, die Sie über den zurückgegebenen Typ haben, in der Typvariablen typeArgument ist? Mir scheint, Sie müssten in diese Variable umwandeln, aber Sie wissen nicht, was es ist. Ich bin mir nicht sicher, ob Sie das mit Reflexion tun können. Eine andere Frage, wenn das Objekt beispielsweise vom Typ int ist, wenn Sie es als Objektvariable an beispielsweise eine Liste <int> übergeben, funktioniert dies? Wird die erstellte Variable als int behandelt?
Theringostarrs
6
@ RobertC.Barth Sie können das "erstellte" Objekt auch im Beispieltyp "dynamisch" anstelle von "Objekt" erstellen. Auf diese Weise können Sie Methoden aufrufen, und die Auswertung wird bis zur Laufzeit verschoben.
McGarnagle
4
@balanza: Sie verwenden MakeGenericMethod.
Jon Skeet
14

Leider nein gibt es nicht. Generische Argumente müssen zur Kompilierungszeit entweder als 1) gültiger Typ oder 2) als anderer generischer Parameter auflösbar sein. Es gibt keine Möglichkeit, generische Instanzen basierend auf Laufzeitwerten zu erstellen, ohne den großen Hammer der Verwendung von Reflektion.

JaredPar
quelle
2

Einige zusätzliche Anweisungen zum Ausführen mit Scherencode. Angenommen, Sie haben eine ähnliche Klasse wie

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

Angenommen, Sie haben zur Laufzeit einen FooContent

Wenn Sie zur Kompilierungszeit binden könnten, würden Sie wollen

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

Allerdings können Sie diese zur Laufzeit nicht. Um dies zur Laufzeit zu tun, würden Sie Folgendes tun:

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

Dynamisch aufrufen Markdown(IEnumerable<FooContent> contents)

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

Beachten Sie die Verwendung von dynamicim Methodenaufruf. Zur Laufzeit dynamicListwird List<FooContent>(zusätzlich auch IEnumerable<FooContent>) sein, da selbst die Verwendung von Dynamic noch in einer stark typisierten Sprache verwurzelt ist, wählt der Laufzeitbinder die entsprechende ausMarkdown Methode aus. Wenn keine genauen Typübereinstimmungen vorhanden sind, wird nach einer Objektparameter-Methode gesucht. Wenn keine übereinstimmen, wird eine Laufzeitbinder-Ausnahme ausgelöst, die darauf hinweist, dass keine Methode übereinstimmt.

Der offensichtliche Nachteil dieses Ansatzes ist ein enormer Verlust an Typensicherheit beim Kompilieren. Mit Code in dieser Richtung können Sie jedoch in einem sehr dynamischen Sinne arbeiten, der zur Laufzeit immer noch vollständig so eingegeben wird, wie Sie es erwarten.

Chris Marisic
quelle
2

Meine Anforderungen waren etwas anders, werden aber hoffentlich jemandem helfen. Ich musste den Typ aus einer Konfiguration lesen und den generischen Typ dynamisch instanziieren.

namespace GenericTest
{
    public class Item
    {
    }
}

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

Zum Schluss nennen Sie es so. Definieren Sie den Typ mit einem Backtick .

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

Wenn Sie wissen, welche Typen übergeben werden, können Sie dies ohne Reflexion tun. Eine switch-Anweisung würde funktionieren. Natürlich würde dies nur in einer begrenzten Anzahl von Fällen funktionieren, aber es wird viel schneller sein als Reflexion.

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
quelle
Dies wird schnell hässlich, sobald Sie anfangen, mit Hunderten von Klassen umzugehen.
Michael G
0

In diesem Snippet möchte ich zeigen, wie eine dynamisch erstellte Liste erstellt und verwendet wird. Zum Beispiel füge ich hier der dynamischen Liste hinzu.

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

Ebenso können Sie jede andere Methode in der Liste aufrufen.

EGN
quelle