Passer un System.Type instancié comme paramètre de type pour une classe générique

183

Le titre est assez obscur. Ce que je veux savoir, c'est si cela est possible:

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

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

Évidemment, MyGenericClass est décrit comme:

public class MyGenericClass<T>

À l'heure actuelle, le compilateur se plaint que 'Le type ou l'espace de noms' myType 'est introuvable. "Il doit y avoir un moyen de le faire.

Robert C. Barth
la source
Génériques! = Modèles. Toutes les variables de type générique sont résolues au moment de la compilation et non au moment de l'exécution. C'est une de ces situations où le type «dynamique» de 4.0 peut être utile.
1
@ Will - de quelle manière? Lorsqu'il est utilisé avec des génériques, sous le CTP actuel, vous finissez essentiellement par appeler les versions <object> (sauf si je manque une astuce ...)
Marc Gravell
@MarcGravell, vous pouvez utiliser foo.Method((dynamic)myGenericClass)pour la liaison de méthode d'exécution, en fait le modèle de localisateur de service pour les surcharges de méthode d'un type.
Chris Marisic
@ChrisMarisic oui, pour certains génériques public void Method<T>(T obj)- une astuce que j'ai utilisée plus de quelques fois au cours des 6 dernières années depuis ce commentaire; p
Marc Gravell
@MarcGravell existe-t-il un moyen de modifier cela pour que la méthode l'instancie?
barlop

Réponses:

220

Vous ne pouvez pas faire cela sans réflexion. Cependant, vous pouvez le faire avec la réflexion. Voici un exemple complet:

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

Remarque: si votre classe générique accepte plusieurs types, vous devez inclure les virgules lorsque vous omettez les noms de type, par exemple:

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);
Jon Skeet
la source
1
OK, c'est bien, mais comment faire pour appeler des méthodes sur créé? Plus de réflexion?
Robert C. Barth
7
Eh bien, si vous pouvez faire en sorte que votre type générique implémente une interface non générique, vous pouvez effectuer un cast vers cette interface. Sinon, vous pouvez écrire votre propre méthode générique qui fait tout le travail que vous voulez faire avec le générique et appeler que la réflexion.
Jon Skeet
1
Ouais, je ne sais pas comment utiliser created si la seule information que vous avez sur le type renvoyé est dans la variable de type typeArgument? Il me semble que vous auriez à utiliser cette variable, mais vous ne savez pas ce que c'est, donc je ne sais pas si vous pouvez le faire avec réflexion. Une autre question si l'objet est par exemple de type int si vous le passez en tant que variable d'objet par exemple dans un List <int> ça fonctionnera? la variable créée sera-t-elle traitée comme un int?
theringostarrs
6
@ RobertC.Barth Vous pouvez également rendre l'objet "créé" dans l'exemple de type "dynamique" au lieu de "objet". De cette façon, vous pouvez appeler des méthodes dessus, et l'évaluation sera différée jusqu'à l'exécution.
McGarnagle
4
@balanza: Vous utilisez MakeGenericMethod.
Jon Skeet
14

Malheureusement non il n'y en a pas. Les arguments génériques doivent pouvoir être résolus au moment de la compilation comme 1) un type valide ou 2) un autre paramètre générique. Il n'y a aucun moyen de créer des instances génériques basées sur des valeurs d'exécution sans le gros marteau de l'utilisation de la réflexion.

JaredPar
la source
2

Quelques instructions supplémentaires pour exécuter avec le code des ciseaux. Supposons que vous ayez une classe similaire à

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

Supposons qu'au moment de l'exécution vous ayez un FooContent

Si vous pouviez vous lier au moment de la compilation, vous voudriez

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

Cependant, vous ne pouvez pas le faire au moment de l'exécution. Pour ce faire au moment de l'exécution, procédez comme suit:

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

Pour invoquer dynamiquement Markdown(IEnumerable<FooContent> contents)

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

Notez l'utilisation de dynamicdans l'appel de méthode. Au moment de dynamicListl' exécution le sera List<FooContent>(en plus aussi IEnumerable<FooContent>) puisque même l'utilisation de dynamic est toujours enracinée dans un langage fortement typé, le classeur d'exécution sélectionnera la Markdownméthode appropriée . S'il n'y a pas de correspondance de type exact, il recherchera une méthode de paramètre d'objet et si aucune ne correspond, une exception de lieur d'exécution sera déclenchée pour avertir qu'aucune méthode ne correspond.

L'inconvénient évident de cette approche est une énorme perte de sécurité de type au moment de la compilation. Néanmoins, un code de ce type vous permettra d'opérer dans un sens très dynamique qui, au moment de l'exécution, est toujours entièrement tapé comme vous vous attendez à ce qu'il soit.

Chris Marisic
la source
2

Mes exigences étaient légèrement différentes, mais j'espère aider quelqu'un. J'avais besoin de lire le type à partir d'une configuration et d'instancier dynamiquement le type générique.

namespace GenericTest
{
    public class Item
    {
    }
}

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

Enfin, voici comment vous l'appelez. Définissez le type avec un backtick .

var t = Type.GetType("GenericTest.GenericClass`1[[GenericTest.Item, GenericTest]], GenericTest");
var a = Activator.CreateInstance(t);
Maître P
la source
0

Si vous savez quels types seront passés, vous pouvez le faire sans réfléchir. Une instruction switch fonctionnerait. Évidemment, cela ne fonctionnerait que dans un nombre limité de cas, mais ce sera beaucoup plus rapide que la réflexion.

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
la source
cela devient très rapide une fois que vous commencez à traiter des centaines de classes.
michael g
0

Dans cet extrait, je veux montrer comment créer et utiliser une liste créée dynamiquement. Par exemple, j'ajoute à la liste dynamique ici.

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

De même, vous pouvez invoquer toute autre méthode de la liste.

EGN
la source