Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic type parameter covariance and multiple interface implementations

If I have a generic interface with a covariant type parameter, like this:

interface IGeneric<out T> {     string GetName(); } 

And If I define this class hierarchy:

class Base {} class Derived1 : Base{} class Derived2 : Base{} 

Then I can implement the interface twice on a single class, like this, using explicit interface implementation:

class DoubleDown: IGeneric<Derived1>, IGeneric<Derived2> {    string IGeneric<Derived1>.GetName()    {      return "Derived1";    }     string IGeneric<Derived2>.GetName()    {      return "Derived2";    }   } 

If I use the (non-generic)DoubleDown class and cast it to IGeneric<Derived1> or IGeneric<Derived2> it functions as expected:

var x = new DoubleDown(); IGeneric<Derived1> id1 = x;        //cast to IGeneric<Derived1> Console.WriteLine(id1.GetName());  //Derived1 IGeneric<Derived2> id2 = x;        //cast to IGeneric<Derived2> Console.WriteLine(id2.GetName());  //Derived2 

However, casting the x to IGeneric<Base>, gives the following result:

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

I expected the compiler to issue an error, as the call is ambiguous between the two implementations, but it returned the first declared interface.

Why is this allowed?

(inspired by A class implementing two different IObservables?. I tried to show to a colleague that this will fail, but somehow, it didn't)

like image 561
SWeko Avatar asked Jan 28 '13 12:01

SWeko


People also ask

What is generic covariance?

Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.

Can a generic implement an interface?

Only generic classes can implement generic interfaces. Normal classes can't implement generic interfaces.

What is the difference between covariance and contravariance?

In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.

What is a generic interface?

A generic interface is primarily a normal interface like any other. It can be used to declare a variable but assigned the appropriate class. It can be returned from a method. It can be passed as argument. You pass a generic interface primarily the same way you would an interface.


1 Answers

If you have tested both of:

class DoubleDown: IGeneric<Derived1>, IGeneric<Derived2> {     string IGeneric<Derived1>.GetName() {         return "Derived1";     }      string IGeneric<Derived2>.GetName() {         return "Derived2";     } }  class DoubleDown: IGeneric<Derived2>, IGeneric<Derived1> {     string IGeneric<Derived1>.GetName() {         return "Derived1";     }      string IGeneric<Derived2>.GetName() {         return "Derived2";     } } 

You must have realized that the results in reality, changes with the order you declaring the interfaces to implement. But I'd say it is just unspecified.

First off, the specification(§13.4.4 Interface mapping) says:

  • If more than one member matches, it is unspecified which member is the implementation of I.M.
  • This situation can only occur if S is a constructed type where the two members as declared in the generic type have different signatures, but the type arguments make their signatures identical.

Here we have two questions to consider:

  • Q1: Do your generic interfaces have different signatures?
    A1: Yes. They are IGeneric<Derived2> and IGeneric<Derived1>.

  • Q2: Could the statement IGeneric<Base> b=x; make their signatures identical with type arguments?
    A2: No. You invoked the method through a generic covariant interface definition.

Thus your call meets the unspecified condition. But how could this happen?

Remember, whatever the interface you specified to refer the object of type DoubleDown, it is always a DoubleDown. That is, it always has these two GetName method. The interface you specify to refer it, in fact, performs contract selection.

The following is the part of captured image from the real test

enter image description here

This image shows what would be returned with GetMembers at runtime. In all cases you refer it, IGeneric<Derived1>, IGeneric<Derived2> or IGeneric<Base>, are nothing different. The following two image shows more details:

enter image description hereenter image description here

As the images shown, these two generic derived interfaces have neither the same name nor another signatures/tokens make them identical.

like image 139
Ken Kin Avatar answered Oct 05 '22 22:10

Ken Kin