Florian Moser
Florian Moser

Reputation: 2663

cast of generic type fails

I am unable to cast a generic type to another generic type, besides the cast should be valid

What I want to archive is in short (for MyModel implementing IModel, and MyImplementation implementing IImplementation):

IImplementation<IModel> implementation = new MyImplementation<MyModel>();
Assert.IsNull(implementation as IImplementation<IModel>);

This is a bit confusing, as the type should be valid.

Complete conceptual model:

interface IModel {}

class MyModel : IModel {}

interface IImplementation<TModel> where TModel : IModel { }

class MyImplementation<TModel> : IImplementation<TModel>
    where TModel : IModel { }

public void CallRegister()
{
    var implementation = new MyImplementation<MyModel>();
    var instance = CastModel(implementation);
    Assert.IsNotNull(instance); //this assert fails!
}

private object CastModel<TModel>(IImplementation<TModel> implementation) where TModel : IModel
{
    return implementation as IImplementation<IModel>;
}

I need this cast to enable me to save multiple IImplementations to the same Dictionary<Type, IImplementation<IModel>>, where the key is obtained by doing typeof(TModel). To do this type safe I don't want to use a Dictionary<Type, object>.

Upvotes: 3

Views: 1278

Answers (2)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112762

This kind of conversion is not allowed for good reasons. Let's take an example where the problem is more obvious. We have the classes Animal, Cat : Animal and Dog : Animal. Now let's do this:

List<Animal> list = new List<Cat>(); // Seems to be possible at first glance.
// An now comes the problem:
list.Add(new Dog());    // Seems to be possible as well.

But wait! The list is in reality a list of cats! And we are trying to add a dog. Even adding new Animal() to list, which is statically typed as List<Animal>, would not work.

Therefore two types T<A> and T<B> are not assignment compatible in C#, even if A and B are!

You need another approach.


What you can do is to wrap your dictionary in a class with a generic method having a generic type constraint.

public class MyImplementationDict
{
    private readonly Dictionary<Type, object> _internalDict = new Dictionary<Type, object>();

    public void Add<T>(IImplementation<T> item)
        where T : IModel
    {
        _internalDict.Add(typeof(T), item);
    }

    ...
}

Upvotes: 5

Eric Lippert
Eric Lippert

Reputation: 660503

Though Olivier's answer gets the idea across about why this usually goes wrong, there is a way to make this work in your program.

The feature you want is called generic interface covariance. Covariance is the property that if a Cat is an Animal, then an IFoo<Cat> is an IFoo<Animal>.

Covariance in C# only works in the following situations:

  • The "outer" type is an interface, delegate or array. No classes or structs.
  • If an interface or delegate, the type must be marked at compile time as supporting covariance. Arrays get (unsafe!) covariance for free.
  • The "inner" types -- the types that are varying -- are both reference types. You can't say that an IFoo<int> is an IFoo<object> even though an int is an object, because they are not both reference types.

To mark an interface as covariant, you put out before the declaration of the type parameter which you wish to allow to vary:

interface IImplementation<out TModel> where TModel : IModel { }

If you do that, your program will start to work.

HOWEVER, out is a reminder to you that covariance is only safe if T is used in output positions. This is legal:

interface I<out T> {
  T M();
}

This is not:

interface I<out T> {
  void M(T t);
}

In the first, T is only passed out of things. In the second, it is passed in.

In the first scenario, we cannot use covariance to introduce a type hole. We have an I<Cat> and we cast it to I<Animal>, and now M returns an Animal, but that's OK, because we already know that it will return a Cat, and a Cat is an Animal.

But in the second scenario, we have the opposite situation. If we allowed an I<Cat> to be converted to I<Animal> then we have an M that can take a Turtle, but the real implementation can only handle Cats. That's why C# will make this illegal.

So go forth and use covariance, but remember that you have to certify to the compiler that you want it, and that it is safe under all circumstances. If you don't want it, or it is not safe, then you don't get to have covariance, and you'll have to find a different solution to your problem.

Upvotes: 7

Related Questions