How to use Activator to create an instance of a generic Type and casting it back to that type?
Asked Answered
A

5

51

I have a generic type Store<T> and use Activator to make an instance of this type. Now how, after using the Activator, can I cast the resulted object of type object back to the instantiated type? I know the type that I used to instantiate the generic. Please see the following code:

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

What I would like to do is something like this:

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

but that doesn't work for obvious reasons. As an alternative I tried:

var store = (Store<IStorable>)x;

which could possibly work in my opinion, but gives an InvalidCastException.

How do I get access again to the Store<T> methods that I know are in the object x?

Anaphrodisiac answered 4/2, 2012 at 12:30 Comment(7)
Can you make obvious reason more obvious? Or just tell us what is the reason. Or do you really mean typeof..., not substituting it with your type?Libation
@Libation that piece of code doesn't compile because Generics don't allow instances of Types to be used as T. I don't know that type, it could be any object that implements the IStorable interface.Anaphrodisiac
What type does the debugger think x is? Is it the right type?Typography
@Carnotaurus, yes the debugger thinks the correct type Store<someObjectThatImplementsIStorable>. So the Activator instantiates the correct object but boxes it in an object, and now I would like to unbox it again.Anaphrodisiac
@Anaphrodisiac box is not the correct term, unless the generic Store type is a struct.Hartz
@phoog, thanks for your correction. It's not a Struct, what would be the correct term to use then?Anaphrodisiac
@Anaphrodisiac Unfortunately I can't think of a concise term to replace "box". The key concept here is that the conversion for reference types is a reference conversion, while a boxing or unboxing conversion is not. I'd change the wording to "the activator instantiates the correct type, but returns a reference of type object, and now I would like to downcast it" (or something like that).Hartz
R
41

Since the actual type T is available to you only through reflection, you would need to access methods of Store<T> through reflection as well:

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 You could also define an additional IStore interface that does not use generics, and uses IStorable instead:

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

Your Store<T> would remain generic, but you would get access to its CountItems by casting to IStore:

var x = (IStore)Activator.CreateInstance(constructedType, new object[] { someParameter });
var count = x.CountItems((IStorable)someObjectThatImplementsStorable);
Rickard answered 4/2, 2012 at 12:44 Comment(7)
I feared it would come down to this. Is it a rule of thumb that when I know the actual type T only through reflection that all access to the actual object need to go through reflection as well?Anaphrodisiac
@Anaphrodisiac Yes, if the only thing you know about someObjectThatImplementsIStorable is that it is an object implementing IStorable, there is little you can do to avoid reflection. If you were getting someObjectThatImplementsIStorable as a generic type T, though, you could cast to Store<T> as well. An alternative would be to avoid generics in a subset of methods (I'll update the answer shortly to show you how).Rickard
I'll go this way, I know I can make this work, and other options don't seem to be available. Thank you for the answer. I'd like to see the subset though, if you have the time. Just out of interest and to learn something.Anaphrodisiac
@Anaphrodisiac I just updated the answer. I've been using the trick with a generic class implementing a regular interface a lot, it is very powerful.Rickard
@dasblinkenlight where it falls down is anywhere where you consume or act on T ie interface IStore { void Add(object item){}} != IStore<T> { void Add(T item){}}, personally this is prettymuch the only places i use generics anywayHalfmast
@dasblinkenlight Indeed I have attempted this path but it didnt work out for me because my Store has a method public T GetSingleByID(int id) that I'd like to access. I can never declare this generic method in a non-generic interface, because the interface wouldn't know about T.Anaphrodisiac
@Anaphrodisiac You can still try it by adding a IStorable GetSingleStorableById(int it) method to your interface, and implementing it by calling GetSingleByID.Rickard
H
7

Cant you just wrap it?

something like

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

or am i missing what you are trying to do?

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

prints

1 
ConsoleApp1.Store'1[ConsoleApp1.Beer]
Halfmast answered 4/2, 2012 at 12:42 Comment(11)
interesting, but then? How do I call this method? can you add a line of sample code?Anaphrodisiac
yeah so long as you call it with any object T is infered, ie Beer item = new Beer(); var beerStore= IConstructStore(item); beerStore.whatever() should work fineHalfmast
typeof(Store<T>) doesn't compile, neither does as Store<T>Anaphrodisiac
I use Visual Studio 2010 and .Net 4. The compile error is The type 'T' cannot be used as type parameter 'T' in the generic type or method 'Store<T>'. There is no boxing conversion or type parameter conversion from 'T' to 'IStorable'.Anaphrodisiac
Ah you added the where section now. Maybe that made a difference. Let me try again.Anaphrodisiac
did you include where T : IStorable on the IConstructStore?Halfmast
Yeah sorry coding from the top of my headHalfmast
Nope no luck, the Activator now makes a Store<IStorable> instead of the Store<objectThatImplementsIStorable>. So now it looks like it is working but the Activator makes the wrong object, I'm expecting a Store<Beer> or any other object that implements IStorable, but your method returns a Store<IStorable> in its stead.Anaphrodisiac
it doesnt for me, ive added a testcase to my answerHalfmast
I figured out what the difference is, in your case you feed the method with a Beer, in my case I feed the method with an IStorable, which in fact is a Beer, but I don't know that at the time that I am calling the method. So where you have Beer beer = new Beer(); I have IStorable objectThatImplementsIStorable;, we both use this as the item parameter.Anaphrodisiac
I found this answer very helpful for my case.Coco
L
3

Most appropriate answer in my opinion would be 'you can't do it in this way'.

You might try introducing an interface IStorage and try making it covariant or contravariant (have you seen that option?). If it is not an option, for example if you have both input and output generic types used in Storage, then there is no way to implement what you want. The reason is that Storage<Beer> cannot be safely used as Storage<IStorable> due to this case:

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

The only possible workaround for you as I see is to move casting out from this method and cast the object in the place where you know all the exact types:

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));
Libation answered 4/2, 2012 at 12:49 Comment(1)
Thank you for your suggestion, I do in fact have input and output generic types in Store<T> so that path doesn't work out. As to the other suggestion, besides an instance of the objectThatImplementsIStorable I do not know the actual Type anywhere. I think I'll end up using Reflection on the object that the Activator made. +1 for good explanation of why the issue exists.Anaphrodisiac
L
1

T must be the type Store<X> avoiding the use of typeof(Store<T>

Libya answered 6/12, 2013 at 13:37 Comment(0)
S
0

Let's say that someObjectThatImplementsIStorable is of type MyStorable.

e.g. MyStorable someObjectThatImplementsIStorable = new MyStorable( ); ... // rest of your code here.

Then x cannot be cast to Store, but it can be cast to Store. The following will work: (Store)x

Note that although MyStorable implements IStorable, there is no relationship between Store and Store. These are two distinct classes that do not derive from each other.

u.

Selfabsorption answered 4/2, 2012 at 12:53 Comment(1)
You cant cast a generic type without including the T, ie (Store)x wont compileHalfmast

© 2022 - 2024 — McMap. All rights reserved.