C# Generic Inheritance and Covariance Part 2

C# generic inheritance and covariance part 2

There does not appear to be any question in this question, so I'll make up a few questions to answer.

What is a covariant conversion?

Let's suppose we have some types Fruit and Apple and Banana with the obvious relationships; Apple is a kind of Fruit, and so on.

A covariant conversion is one where the convertability of the type argument implies the convertibility of the generic type. If Apple is convertible to Fruit, and Bowl<Apple> is convertible to Bowl<Fruit>, then Bowl<T> is covariant in T.


What is a contravariant conversion?

A contravariant conversion is a covariant conversion that reverses the direction instead of preserving it. If Eater<Fruit> is convertible to Eater<Apple> then Eater<T> is contravariant in T.


How do I mark an interface or delegate as being covariant or contravariant in its type parameters?

Covariant type parameters are marked out and contravariant type parameters are marked in.

This is intended to be mnemonic: covariant interfaces typically have the type parameter appear in output positions and contravariant interfaces typeically have the type parameter appear in input positions.


String is convertible to Object. How can I make IReadOnlyCollection<String> convertible to IReadOnlyCollection<Object>?

Make IReadOnlyCollection<T> covariant in T. Mark it out.


Consider the following code:

delegate void Action<in T>(T t);
interface IFoo<in X>
{
void M(Action<X> action);
}

Why does the compiler say that this is not valid?

Because it is not valid. Let's see why.

class Foo : IFoo<Fruit>
{
public void M(Action<Fruit> action)
{
action(new Apple()); // An apple is a fruit.
}
}
...
IFoo<Fruit> iff = new Foo();
IFoo<Banana> ifb = iff; // Contravariant!
ifb.M(banana => { banana.Peel(); });

Follow the logic. This program passes an apple as the "this" of Banana.Peel(), which is clearly wrong.

The compiler knows that this can happen, and so disallows the interface to be declared in the first place.


What should I do if I have more questions about variance?

You should start by reading my articles on the design and implementation of the feature. Start from the bottom; they are listed in reverse chronological order:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

If you still have a question then you should post questions here that actually contain questions, instead of making people guess what the question really is.

C# generic inheritance and covariance

The line:

ICollection<Object> s = new Collection<String>();

(comments) would work with out variance; however, DoSomething(ItemType) would require in variance; so that type can be neither in nor out: variance does not apply here.

The way that is usually handled is by having a generic and non-generic API. People interested in the specific types can use the generic API; people just interested in "an object" can use the non-generic API.

For illustration:

interface ICollection
{
void DoSomething(object item);
}
interface ICollection<ItemType> : ICollection
{
void DoSomething(ItemType item);
}

class Collection<ItemType> : ICollection<ItemType>
{
void ICollection.DoSomething(Object item)
{
DoSomething((ItemType)item);
}
public void DoSomething(ItemType item)
{
//...
}
}

Then this works:

ICollection s = new Collection<String>();
object o = "abcd";
s.DoSomething(o);

Why is using a variable of a generic interface with a concrete class instance inheriting the interface fail in C#?

That's because of co- and contra-variance.

When something is a IFooWrapper<Foo>, that doesn't mean it's could be compile time converted to IFooWrapper<IFoo>. I think it's better explained here: Covariance and Contravariance (C#) | Microsoft Docs and still confused about covariance and contravariance & in/out.

Please note that you could mark interfaces in C# as co- or contra-variant. For example, if you have the below interface (notice the out I - it's now covariant for type parameter I), you could use IFooWrapper<IFoo> foo = new FooWrapper();

public interface IFooWrapper<out I> where I : IFoo { }

See demo in Fiddle

Instead of changing the interface, you could fix the method signature with generics:

void Register<T>(IFooWrapper<T> foo) where T: IFoo
{
}

That one could be called with IFooWrapper<Foo> and IFooWrapper<IFoo> - so this works: Register(new FooWrapper());

See also Fiddle

Generic type parameter covariance and multiple interface implementations

The compiler can't throw an error on the line

IGeneric<Base> b = x;
Console.WriteLine(b.GetName()); //Derived1

because there is no ambiguity that the compiler can know about. GetName() is in fact a valid method on interface IGeneric<Base>. The compiler doesn't track the runtime type of b to know that there is a type in there which could cause an ambiguity. So it's left up to the runtime to decide what to do. The runtime could throw an exception, but the designers of the CLR apparently decided against that (which I personally think was a good decision).

To put it another way, let's say that instead you simply had written the method:

public void CallIt(IGeneric<Base> b)
{
string name = b.GetName();
}

and you provide no classes implementing IGeneric<T> in your assembly. You distribute this and many others implement this interface only once and are able to call your method just fine. However, someone eventually consumes your assembly and creates the DoubleDown class and passes it into your method. At what point should the compiler throw an error? Surely the already compiled and distributed assembly containing the call to GetName() can't produce a compiler error. You could say that the assignment from DoubleDown to IGeneric<Base> produces the ambiguity. but once again we could add another level of indirection into the original assembly:

public void CallItOnDerived1(IGeneric<Derived1> b)
{
return CallIt(b); //b will be cast to IGeneric<Base>
}

Once again, many consumers could call either CallIt or CallItOnDerived1 and be just fine. But our consumer passing DoubleDown also is making a perfectly legal call that could not cause a compiler error when they call CallItOnDerived1 as converting from DoubleDown to IGeneric<Derived1> should certainly be OK. Thus, there is no point at which the compiler can throw an error other than possibly on the definition of DoubleDown, but this would eliminate the possibility of doing something potentially useful with no workaround.

I have actually answered this question more in depth elsewhere, and also provided a potential solution if the language could be changed:

No warning or error (or runtime failure) when contravariance leads to ambiguity

Given that the chance of the language changing to support this is virtually zero, I think that the current behavior is alright, except that it should be laid out in the specifications so that all implementations of the CLR would be expected to behave the same way.

Contra/Co Variance for a list of generic types assuming base interface inheritance

What you need is for you IManager to be covariant.

interface IManager<out TEvent> where TEvent : IEvent

That way this assignment becomes legal:

IManager<IEvent> manager = new Manager<IMoreSpecificEvent>();

Covariance, in the most basic sense, specifies that you don't care if the type parameter is more derived than the constraint for such assignment. That means that, among other things, nowhere in a covariant implementation (so in your Manager
You can read more about covariance in C# here.

EDIT:

Right, so this method:

void Foo(TEvent mySpecificEvent);

ruins our little plan here. Right now your Manager is not covariant, and for a good reason. Let's consider a list:

List<IManager<IEvent>> list;

and say that this assignment is legal (it is not, but let's pretend) :

IManager<ISpecificNumericEvent> manager = new SpecificNumericManager();

list.Add(manager);

What happens if we pass this list around to do some operations on the manager? Well, all we know is that a thing inside is of type IManager<IEvent>. That means that we should be able to feed it any IEvent into the Foo method, right?

class BaseEvent : IEvent {}

foreach(var manager in list)
{
IEvent event = new BaseEvent();

manager.Foo(event);
}

But since we have earlier assigned a SpecificNumericManager to the list, we're trying to call a method with a signature:

void Foo(TEvent event) where TEvent : ISpecificNumericEvent

with a BaseEvent, which does not implement ISpecificNumericEvent. Boom, the type system is destroyed in a fiery explosion, cats and dogs living together, mass hysteria.

This interface cannot be covariant for the reasons given above - C# simply disallows the bypassing of the type system earlier, disallowing a covariant assignment toIManager<IEvent>. For the same reasons a for-loop feeding events to generic IManager<IEvent>s would be a bad idea, because there is simply no idea of knowing whether a particular manager will be able to handle that particular event at compile-time. You need to think about what you're really trying achieve with that loop. Usually, when covariance/contravariance rules are broken, it's because the thing you want to do really does not make sense. If, however, you want to do some crazy stuff on types on a generic IManager<IEvent> list, then you won't have the type system on your side and will have to use reflection to achieve your insidious goals.

How to implement generic polymorphism in c# - Part 2?

First of all you get the idea of generics wrong.

If you Look at Foo its a generic type.
Foo and Foo are NEW types, they do not derive from List, neither the types are conneted by means of inheritance. Using generics creates new types, its does not derive!

However what you are looking for is Covariance and Contravariance. This will allow you to create a kind of "generic polymorphism", but for that you need to specify this within your generic definition. Therefore it will only work for very few framework generics out of the box.

class Program
{
static void Main(string[] args)
{
IManager<IIdentifier> f1 = new C1();
IManager<IIdentifier> f2 = new SpecificEntityManager(); //IManager<ISpecificEntity>
}
}

interface IIdentifier { }
interface ISpecificEntity : IIdentifier { }
interface IManager<out T> { }

class C1 : IManager<IIdentifier> { }
class SpecificEntityManager : IManager<ISpecificEntity> { }

Here is what YOU have to change:

internal interface IContainer<out TIdentifier> where TIdentifier : IIdentifier 
{
TIdentifier GetEntity();
}
internal interface IManager<out TIdentifier> where TIdentifier : IIdentifier
{
IContainer<IIdentifier> Container { get; }
}
internal class SpecificEntityManager : IManager<ISpecificEntity>
{
public IContainer<IIdentifier> Container { get; set; }
}
internal class AnotherSpecificEntityManager : IManager<IAntoherSpecificEntity>
{
public IContainer<IIdentifier> Container { get; set; }
}

How to make a generic class with inheritance?

you could always do this

List<A> testme = new List<B>().OfType<A>().ToList();

As "Bojan Resnik" pointed out, you could also do...

List<A> testme = new List<B>().Cast<A>().ToList();

A difference to note is that Cast<T>() will fail if one or more of the types does not match. Where OfType<T>() will return an IEnumerable<T> containing only the objects that are convertible



Related Topics



Leave a reply



Submit