MarcT
MarcT

Reputation: 178

Casting a collection's generics implicitly

Last night I learned about this wonderful operation of casting by example: a very cool way to generate a collection of some Type using a reference to an existing instance.

My problem is that although this works when you explicitly create the instance, the Type of collection produced is inaccurate if you use activator to instantiate from a Type.

    class TestCollectionContent
    {
        public int id { get; private set; }
    }

    [Test]
    public void TestListCastCreation()
    {
        var explicitCast = new TestCollectionContent ();    //This casts as TestCollectionContent
        var explicitList = MakeList (explicitCast);         //This casts as List<CommandWithExecute>
        explicitList.Add (new TestCollectionContent ());

        Type clazz = typeof(TestCollectionContent);
        var implicitCast = Activator.CreateInstance (clazz);//This casts as TestCollectionContent
        var implicitList = MakeList (implicitCast);         //This casts as List<object>
        implicitList.Add (new TestCollectionContent ());

        Assert.AreEqual (explicitCast.GetType (), implicitCast.GetType ()); //Succeeds
        Assert.AreEqual (explicitList.GetType (), implicitList.GetType ()); //FAILS!
    }

    public static List<T> MakeList<T>(T itemOftype)
    {
        List<T> newList = new List<T>();
        return newList;
    } 

For my purpose it is imperative that the collection be correctly cast. Any thoughts?

Note that I'm using C# with Unity3D (which uses something akin to .Net 3.5).

Upvotes: 1

Views: 71

Answers (2)

Tim S.
Tim S.

Reputation: 56536

This code behaves this way because T is inferred at compile time, not run time. Since implicitCast is of type object, it compiles with MakeList<object>.

var implicitList = MakeList (implicitCast); // equivalent to
List<object> implicitList = MakeList<object>(implicitCast);

var explicitList = MakeList (explicitCast); // equivalent to
List<TestCollectionContent> explicitList =
                   MakeList<TestCollectionContent>(explicitCast);

If you want it to use the runtime type, you can use reflection or dynamic.

Upvotes: 0

poke
poke

Reputation: 387677

Activator.CreateInstance always returns an object, so you will not get any static type information from it when using it. This will make the variable implicitCast of type object although its value is of a more specialized type.

Now when using generics, only the type that is available for static typing is taken into account. So when passing implicitCast to MakeList, all that method sees is an object. As such, the method will be called as MakeList<object> and will return a List<object>, which is of course not of the same type as explicitList.

Unfortunately (or fortunately?) you cannot really do this any better. Generics are supposed to be something for use in a static typing environment, and if you start to create types dynamically, you will lose this ability.

You could however use Activator.CreateInstance for the list creation just as well by doing something like this:

public static IList MakeList(object itemOftype)
{
    Type listType = typeof(List<>).MakeGenericType(itemOfType.GetType());
    return (IList) Activator.CreateInstance(listType);
}

Of course, this will also just return an object, so you will have to cast it to a more specialized type, or use the non-generic IList interface to have at least some access to it.

Upvotes: 1

Related Questions