Passa un sistema istanziato. Digita come parametro di tipo per una classe generica


182

Il titolo è piuttosto oscuro. Quello che voglio sapere è se questo è possibile:

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

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

Ovviamente, MyGenericClass è descritto come:

public class MyGenericClass<T>

In questo momento, il compilatore si lamenta che "Il tipo o lo spazio dei nomi" myType "non è stato trovato." Deve esserci un modo per farlo.


Generics! = Modelli. Tutte le variabili di tipo generico vengono risolte in fase di compilazione e non in fase di esecuzione. Questa è una di quelle situazioni in cui il tipo "dinamico" di 4.0 può essere utile.

1
@Will - in che modo? Se usato con generici, sotto l'attuale CTP si finisce essenzialmente per chiamare le versioni <object> (a meno che non mi manchi un trucco ...)
Marc Gravell

@MarcGravell è possibile utilizzare foo.Method((dynamic)myGenericClass)per l'associazione del metodo di runtime, in modo efficace il modello di localizzazione del servizio per i sovraccarichi del metodo di un tipo.
Chris Marisic,

@ChrisMarisic sì, per alcuni generici public void Method<T>(T obj)- un trucco che ho usato più di un paio di volte negli ultimi 6 anni da quel commento; p
Marc Gravell

@MarcGravell c'è un modo per modificarlo in modo che il metodo lo crei un'istanza?
barlop

Risposte:


220

Non puoi farlo senza riflessione. Tuttavia, puoi farlo con la riflessione. Ecco un esempio 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 la classe generica accetta più tipi, è necessario includere le virgole quando si omettono i nomi dei tipi, ad esempio:

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);

1
OK, va bene, ma come si fa a chiamare i metodi su creati? Più riflessione?
Robert C. Barth,

7
Bene, se riesci a far sì che il tuo tipo generico implementi un'interfaccia non generica, puoi trasmettere a quell'interfaccia. In alternativa, è possibile scrivere il proprio metodo generico che fa tutto il lavoro che si vuole fare con il generico, e chiamare che con la riflessione.
Jon Skeet,

1
Sì, non capisco come utilizzare creato se le uniche informazioni che hai sul tipo restituito sono nella variabile typeArgument? Mi sembra che dovresti eseguire il cast su quella variabile, ma non sai cosa sia, quindi non sono sicuro che puoi farlo con la riflessione. Un'altra domanda se l'oggetto è ad esempio di tipo int se lo si passa come variabile oggetto per dire ad esempio un elenco <int> funzionerà? la variabile creata verrà trattata come int?
theringostarrs,

6
@ RobertC.Barth Puoi anche rendere l'oggetto "creato" nel tipo di esempio "dinamico" anziché "oggetto". In questo modo è possibile richiamare i metodi su di esso e la valutazione verrà rinviata fino al runtime.
McGarnagle,

4
@balanza: usi MakeGenericMethod.
Jon Skeet,

14

Purtroppo no non c'è. Gli argomenti generici devono essere risolvibili al momento della compilazione come 1) un tipo valido o 2) un altro parametro generico. Non c'è modo di creare istanze generiche basate sui valori di runtime senza il grande martello dell'uso della riflessione.


2

Qualche ulteriore come eseguire con il codice delle forbici. Supponiamo di avere una classe simile a

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

Supponiamo che in fase di esecuzione tu abbia un FooContent

Se fossi in grado di eseguire il binding in fase di compilazione, lo vorresti

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

Tuttavia non è possibile farlo in fase di esecuzione. Per fare questo in fase di esecuzione, si dovrebbe fare come segue:

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

Invocare dinamicamente Markdown(IEnumerable<FooContent> contents)

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

Si noti l'uso di dynamicnella chiamata del metodo. In fase di esecuzione dynamicListsarà List<FooContent>(inoltre anche IEnumerable<FooContent>) dal momento che anche l'uso dinamico è ancora radicato in una lingua fortemente tipizzata, il raccoglitore runtime selezionerà il Markdownmetodo appropriato . Se non ci sono corrispondenze esatte di tipi, cercherà un metodo con parametri oggetto e se nessuna delle due corrisponderà a un'eccezione del raccoglitore di runtime verrà sollevata avvisando che nessun metodo corrisponde.

L'ovvio inconveniente di questo approccio è un'enorme perdita di sicurezza del tipo in fase di compilazione. Tuttavia, il codice lungo queste linee vi consentirà di operare in un senso molto dinamico che in fase di runtime è ancora completamente digitato come previsto.


2

Le mie esigenze erano leggermente diverse, ma speriamo che possano aiutare qualcuno. Avevo bisogno di leggere il tipo da una configurazione e creare un'istanza del tipo generico in modo dinamico.

namespace GenericTest
{
    public class Item
    {
    }
}

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

Infine, ecco come lo chiami. Definire il tipo con un backtick .

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

0

Se sai quali tipi verranno passati, puoi farlo senza riflettere. Un'istruzione switch funzionerebbe. Ovviamente, questo funzionerebbe solo in un numero limitato di casi, ma sarà molto più veloce della riflessione.

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

questo diventa brutto veloce quando inizi a trattare con centinaia di classi.
michael g

0

In questo frammento voglio mostrare come creare e utilizzare un elenco creato dinamicamente. Ad esempio, sto aggiungendo all'elenco dinamico qui.

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

Allo stesso modo è possibile invocare qualsiasi altro metodo nell'elenco.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.