web-dev-qa-db-fra.com

Comment utiliser Activator pour créer une instance d'un type générique et la restituer à ce type?

J'ai un type générique Store<T> et utilisez Activator pour créer une instance de ce type. Maintenant, comment, après avoir utilisé l'activateur, puis-je reconstituer l'objet résultant de type object en type instancié? Je connais le type que j'ai utilisé pour instancier le générique. Veuillez consulter le code suivant:

class Store<T> where T : IStorable 
{}

class Beer : IStorable 
{}

class BeerStore : Store<Beer>
{}

Type storeType = someObjectThatImplementsIStorable.GetType();
Type classType = typeof(Store<>);
Type[] typeParams = new Type[] { storeType };   
Type constructedType = classType.MakeGenericType(typeParams);

object x = Activator.CreateInstance(constructedType, new object[] { someParameter });

Ce que je voudrais faire, c'est quelque chose comme ceci:

var store = (Store<typeof(objectThatImplementsIStorable)>)x;

mais cela ne fonctionne pas pour des raisons évidentes. Comme alternative, j'ai essayé:

var store = (Store<IStorable>)x;

qui pourrait éventuellement fonctionner à mon avis, mais donne un InvalidCastException.

Comment puis-je accéder à nouveau au Store<T> les méthodes que je connais sont dans l'objet x?

35
Bazzz

Étant donné que le type réel T n'est disponible que par réflexion, vous devez accéder aux méthodes de Store<T> par la réflexion aussi:

Type constructedType = classType.MakeGenericType(typeParams);

object x = Activator.CreateInstance(constructedType, new object[] { someParameter });
var method = constructedType.GetMethod("MyMethodTakingT");
var res = method.Invoke(x, new object[] {someObjectThatImplementsStorable});

EDIT Vous pouvez également définir une interface IStore supplémentaire qui n'utilise pas de génériques et utilise à la place IStorable:

interface IStore {
    int CountItems(IStorable item);
}
class Store<T> : IStore where T : IStorable {
    int CountItems(IStorable item) {
        return count;
    }
}

Votre Store<T> resterait générique, mais vous auriez accès à son CountItems en effectuant un cast vers IStore:

var x = (IStore)Activator.CreateInstance(constructedType, new object[] { someParameter });
var count = x.CountItems((IStorable)someObjectThatImplementsStorable);
30
dasblinkenlight

Vous ne pouvez pas simplement l'envelopper?

quelque chose comme

public Store<T> IConstructStore<T>(T item) where T : IStorable 
{
 return Activator.CreateInstance(typeof(Store<T>), new object[] { someParameter }) as Store<T>;
}

ou est-ce que je manque ce que vous essayez de faire?

IE

class Program
{
    static void Main(string[] args)
    {
        Beer b = new Beer();
        var beerStore = IConstructStore(b);
        Console.WriteLine(beerStore.test);
        Console.WriteLine(beerStore.GetType().ToString());
    }

    public static Store<T> IConstructStore<T>(T item) where T : IStorable
    {
        return Activator.CreateInstance(typeof(Store<T>), new object[] { }) as Store<T>;
    }
}

interface IStorable { }

class Store<T> where T : IStorable
{
    public int test = 1;
}

class Beer : IStorable
{ }

impressions

1 
ConsoleApp1.Store'1[ConsoleApp1.Beer]
4
Luke McGregor

La réponse la plus appropriée à mon avis serait "vous ne pouvez pas le faire de cette manière".

Vous pourriez essayer d'introduire une interface IStorage et essayer de la rendre covariante ou contravariante (avez-vous vu cette option?). Si ce n'est pas une option, par exemple si vous avez à la fois des types génériques d'entrée et de sortie utilisés dans Storage, il n'y a aucun moyen d'implémenter ce que vous voulez. La raison en est que Storage<Beer> ne peut pas être utilisé en toute sécurité en tant que Storage<IStorable> en raison de ce cas:

Storage<IStorable> store = new Storage<Beer>(); // let's pretend we can do it 
store.Save(new StorableButNotBeer()); // what will happen here?

La seule solution de contournement possible pour vous, comme je le vois, consiste à déplacer la conversion de cette méthode et à lancer l'objet à l'endroit où vous connaissez tous les types exacts:

public void object CreateStore(Type istorableType)
{
    // here is your activator code, but you will have to return an object
}

var beerStore = (Store<Beer>)CreateStore(typeof(Beer));
3
Snowbear

T doit être le type Store en évitant l'utilisation de typeof (Store

Disons que someObjectThatImplementsIStorable est de type MyStorable.

par exemple. MyStorable someObjectThatImplementsIStorable = new MyStorable (); ... // reste de votre code ici.

Ensuite, x ne peut pas être converti en magasin, mais il peut être converti en magasin. Les éléments suivants fonctionneront: (Store) x

Notez que bien que MyStorable implémente IStorable, il n'y a aucune relation entre Store et Store. Ce sont deux classes distinctes qui ne dérivent pas l'une de l'autre.

u.

0
Uri