Covariance and Contravariance in C#, Part Ten: Dealing With Ambiguity


OK, I wasn’t quite done. One more variance post!

Smart people: suppose we made IEnumerable<T> covariant in T. What should this program fragment do?

class C : IEnumerable<Giraffe>, IEnumerable<Turtle> {
    IEnumerator<Giraffe> IEnumerable<Giraffe>.GetEnumerator() {
        yield return new Giraffe();
    }
    IEnumerator<Turtle> IEnumerable<Turtle>.GetEnumerator() {
        yield return new Turtle();
    }
// [etc.]
}
 
class Program {
    static void Main()  {
        IEnumerable<Animal> animals = new C();
        Console.WriteLine(animals.First().GetType().ToString());
    }
}

Options:

1) Compile-time error.
2) Run-time error.
3) Always enumerate Giraffes.
4) Always enumerate Turtles.
5) Choose Giraffes vs Turtles at runtime.
6) Other, please specify.

And if you like any options other than (1), should this be a compile-time warning?

Comments (43)

  1. 1. Compile time error.

    Also,

    object o = new C();

    var e = (IEnumerable<Animal>) o;

    should be a runtime error.

    Some sticky situations are harder, though. For example, is it possible to make this work?

    IEnumerable<Animal> a1 = (IEnumerable<Giraffe>) new C();

    IEnumerable<Animal> a2 = (IEnumerable<Turtle>) new C();

    Console.WriteLine(a1.First().GetType().ToString()); // Giraffe

    Console.WriteLine(a2.First().GetType().ToString()); // Turtle

  2. mstrobel says:

    There is no reasonably obvious expected behavior if that code were to compile, so it would have to be a compile time error.

  3. David E says:

    My uneducated guess is 3) Always enumerate Giraffes.

    Being as C inherits from IEnumerable<Giraffe> first, this is the less ambiguous option.

  4. Bryan Watts says:

    1. Compile time error (gut reaction)

    Thinking outside the box, though, I wonder…

    Could we instead use this as an opt-in to aggregation? Something along the lines of

    IEnumerable<Animal> animals = new C();

    // Both signatures qualify, interface list determines order

    foreach(Animal a in animals)

    {

     Console.WriteLine(a.GetType().ToString());

    }

    "Giraffe"

    "Turtle"

    There has to be a reason why that’s wrong 🙂

  5. bleroy says:

    Absolutely compile-time. Anything else would be super-confusing.

  6. Chuck England says:

    In the example above, didn’t the programmer mean IEnumberable<Animal>?

    Or, perhaps IEnumerable<T> where T : Giraffe, Turtle?

    But, assuming this is a more legitimate case of two IEnumerable<> types, it seems the compiler should support this. (Not a compile time error.)

    The LHS is requesting (in the example) a very specific interface. The IEnumerable<Turtle> interface. At compile time, it seems plausible that this type could be deduced. In fact, you should be able to follow this up the heirarchy to IEnumerable<Animal> and so on.

    Chuck (crystal_bit@hotmail.com)

  7. Stefan Wenig says:

    Good question. Makes me wonder how variance is implemented on vtable level…

    1 – compile time error whenever it can be detected (and runtime error otherwise)

    Additionally, if it were not for backwards compatibility, I’m not sure if it’s necessary to allow the implementation of a covariant interface for two Ts that share a common base type. Too confusing, too hard to guess what happens, and prone to cause runtime-errors. Maybe a warning would be a good idea: "CSxxxx – Don’t implement covariant interfaces that way unless you _really_ know what you’re doing." On the other hand, while only true for the special case of IEnumerable<T>: if you implement IEnumerable<T> for two different Ts, you still have to implement the non-generic IEnumerable. If that doesn’t tip you off, you might as well accept the punishment 😉

    And yes, your blog is fascinating enough that I care to read and comment although your policy of not answering most of my comments has not changed so far. It could be so much better though.

  8. Thomas Danecker says:

    I agree with Stuart Ballard:

    The compiler should in no case choose one over the other -> compile time error.

    The more specialiced case would be, what if there’s an implicit and an explicit interface implemenation?

    class C : IEnumerable<Giraffe>, IEnumerable<Turtle> {

       IEnumerator<Giraffe> GetEnumerator() {

           yield return new Giraffe();

       }

       IEnumerator<Turtle> IEnumerable<Turtle>.GetEnumerator() {

           yield return new Turtle();

       }

    // [etc.]

    }

    class Program {

       static void Main()  {

           IEnumerable<Animal> animals = new C();

           Console.WriteLine(animals.First().GetType().ToString());

       }

    }

    Should the compiler choose the implicit implementation or also cause a compiler error?

    Another solution comes into my mind:

    What if the compiler would issue an error because the class implements two "overlapping" covariant interfaces? The two covariant interfaces IEnumerable<Giraffe> and IEnumerable<Turtle> should maybe have some kind of equivalence relation in the context of implementing them by a class.

  9. Peter Ritchie says:

    I would expect to respond in the same way it responds with any other ambiguity: compile-error.

    This is an interesting scenario.  What you’re essentially allowing programmers to implement would be similar to:

    interface IBase

    {

    int Method ( );

    }

    interface IOne : IBase

    {

    }

    interface ITwo : IBase

    {

    }

    class OneClass : IOne, ITwo

    {

    int IOne.Method ( )

    {

    return 1;

    }

    int ITwo.Method ( )

    {

    return 2;

    }

    }

    …which, of course, is not syntactically correct.

  10. Stefan Wenig says:

    Additional question (I guess it’s really the same question as the vtable implementation): Is an assignment of an IEnumerable<Giraffe> to an IEnumerable<Animal> variable statically verifyable, i.e. is it just copying a DWORD at runtime, or do you have to check or even convert the assignment?

    Either way, I’d argue that implementing IEnumerable<T> for Giraffe AND Turtle should break covariance. I noted that the CLR currently chooses option 3 (first interface implemented), and I really think this is dangerous. I know I’m asking a lot for breaking changes, this looks wrong. I say discourage these implementations (using warnings), and break covariance if the programmer insists.

  11. Daniel Grunwald says:

    Exactly the same problem already exists with user-defined conversions:

    class Program

    {

    public static void Main(string[] args)

    {

    Animal a = new SomeClass();

    Console.WriteLine(a.GetType().Name);

    }

    }

    abstract class Animal { }

    class Giraffe : Animal { }

    class Turtle : Animal { }

    class SomeClass

    {

    public static implicit operator Giraffe(SomeClass c)

    {

    return new Giraffe();

    }

    public static implicit operator Turtle(SomeClass c)

    {

    return new Turtle();

    }

    }

    It results in a compile time error (ambiguous user-defined operators SomeClass->Giraffe and SomeClass->Turtle), so that’s what should happen for the covariant IEnumerable, too.

  12. Pop Catalin says:

    It can’t be compile time error because of the philosophy that adding an interface to a class in a library should never break client code.

    Also you can’t choose between giraffes and turtles  neither at runtime or compile time for the very same reason. An library implementer might change the order of interface declarations and not preserve it.

    The only solution I see is to allow only one ‘implicit’ interface implementation for a covariant interface and the others should be ‘explicit’. Why? Because both IEnumerable<Giraffe> and IEnumerable<Turtle> are the same as IEnumerable<Animal> when T is covariant, basically implementing IEnumerable<Animal> twice,  with the exception that they carry extra type info that can be used to skip casts.

    {

    Other solution might be not to mark IEnumerable<T+>  as covariant but to mark it in the implemented class

    class C : IEnumerable<Giraffe+>, IEnumerable<Turtle>  

    where IEnumerable<Giraffe+> is now covariant? (uh those terms make my head spin)

    }

  13. Fabrice says:

    Definitely 1 (compile-time error). Please avoid confusion whenever possible. We don’t want programming to become gambling, do we?

  14. Tanveer Badar says:

    It should be a compile time error. Because when you consider code like

    IEnumerable<Animal> animals = c;

    multiple paths exist for the conversion to succeed. Compiler should die the death of Buridan’s ass, since there is no motivation to choose one conversion over the other.

    This problem readily manifests itself in C++ where multiply inheriting a class is allowed, the famous "diamond" hierarchies of inheritance. In CLR, I think it is the only closest feature to diamond hierarchies.

  15. Without further language enhancements, in the face of non(-obvious)-determinism, I say…

    Compile time error.

    Except it is possible to image a scenario where such a setup would be useful.  Consider the enumerable to be a producer of some sort of "Item"s.  As a handy feature, it can directly produce the appropriately subclassed "SmallItem" or "ScalableItem".  Such a producer might implement IEnumerable<SmallItem> and IEnumerable<ScalableItem>  such that that the consumer can get the appropriately formed items he needs easily.

    Currently, a software doing something like that would do so precisely to do a poor-mans covariance, and is trying to be useful while at it.  A feature which saves the consumer effort today, (by allowing the IEnumerable client to just pick what’s needed), would turn into a confusing situation for in this version – why can I do covariance casting on "almost" all interface implementations, but not this one?

    This situation, in essence, already exists today, and the solution there is the right one.

    If you want to implement IEnumerable<a> and IEnumerable<b>, what do you do in a foreach?  You iterate over the IEnumerable – i.e. you force the interface implementor to choose.

    Ideally, if you implement two interfaces which are potentially covariant, you should at the least be forced to prioritize (in some fashion), and at worst to implement the border case explicitly (nasty, not preferred).  Of course, that means that in a hypothetical future CLR marking IEnumerable<T> covariant would be a (massively) breaking change…  that’s why I’m saying ideally, since that’s probably not reasonable.

  16. Jon Skeet says:

    Another vote for compile-time error. It’s pretty rare to see so much agreement on a language topic – I think the consensus is clear on this one 🙂

    Jon

  17. Ben says:

    It depends on your First method, which you omitted.

    I am away from the VS2008 beta at the moment.

    Is it an extension method on IEnumerable<T>?

    Is it supposed to return the first _implemented_ type? That seems a little ridiculous, but that is is up to the definition of the method.

    Besides the assignability of C to IEnumerable<Animal> I do not think that this example has any relevance on variance. The fact that C simultaneously implements IEnumerable<Giraffe> and IEnumerable<Turtle> seems to be a poor design, but from the language point of view it is certainly legal regardless of variance. There may even be appropriate uses for it (I do not know).

    Thus, I will have to choose "6) Other, please specify": clean compile, no error, warning, or run-time exception. Depending on the definition of First, either Giraffe or Turtle would be acceptable. If you can explain the definition of First, I will try to give a better answer.

  18. Jon Skeet says:

    First extends IEnumerable<T> and returns a T – the first one in the sequence.

    So, which would it return? A Turtle or a Giraffe? I can’t see how it could do anything other than be a compilation error. Picking one sequence over the other arbitrarily would be disasterous, IMO.

    Jon

  19. Richard says:

    What Peter Ritchie said. You’re providing two different IEnumerable<Animal> implementations, so the user has to specify which one they want (by casting the C to IEnumerable<T> for T in {Giraffe, Turtle}).

  20. James Birchall says:

    I wonder if it would be possible to return a "multi-type" variable?  Ie// one that is either a Giraffe or a Turtle, but never a "Lion" or an "Alligator".  

    To me, at least, this code is trying to tell you that the container can hold only Giraffes and Turtles and nothing else from the set of Animals (perhaps because storing Giraffes and Lions in the same container leads to bad results).  Enumerating through the container should get a single aggregate set of both Turtles and Giraffes, depending upon what was stored in there.  Trying to store a Lion in that code should fail at compile time but adding either Turtles or Giraffes should be just fine.

    In that conceptual model, the "First().GetType()" call should return a hybrid type of "Giraffe" | "Turtle" in the generic sense, but a specific "Giraffe" or "Turtle" in the specific case depending upon the type stored in the first element.

    Is there a better way to express this programatic desire?  Currently, the only way to put N siblings together is to either implement a seperate container for each, to include the base class or to implement some middle "GirraffeAndTurtle" class which inherits from "Animal" and from which both Turlte and Giraffe are derived.  It would be nice to have something clearer.

    Maybe something notationally like:

    Class C: IEnumerable<Turtle | Giraffe> {

    IEnumerator<Giraffe | Turtle> IEnumerable<Giraffe | Turtle>.GetEnumerator() {

          if (current_obj is Giraffe)

           yield return new Giraffe(current_obj);

          if (current_obj is Turtle)

            yield return new Turtle(current_obj);

          else throw new IncompatibleAnimalException();

       }

    }

    class Program {

       static void Main()  {

           #success

           IEnumerable<Animal+> animals = new C();

           #All the "giraffes"’ stored in the set

           IEnumerable<Giraffe> giraffes = new C();

           #All the "Turtles" stored in the set

           IEnumerable<Turtle> turtles = new C();

           # Breaks at compile time

           IEnumerable<Lion> lions = new C():

           Console.WriteLine(animals.First().GetType().ToString());

       }

    }

  21. Peter Ritchie says:

    James: if there were a "multi-type" return, what would it mean to call its "Reproduce" method, considering the Giraffe is a mammal that births its young and the turtle is a reptile that lays eggs.  At some point you need to tell the compiler which of the two types you want to deal with.

  22. Craig Porters says:

    I also do not understand First.

    I assume it is a (pseudo) extension method, but exactly what is it suppose to do?

    I briefly looked for some documentation but to no avail.

    Jon Skeet wrote:

    > First extends IEnumerable<T> and returns a T – the first one in the sequence.

    I still do not understand. How would First, a method, extend the type IEnumerable<T>? And if First is a type, then how is it going to return a T? In particular, please expand on "it returns a T – the first one in the sequence". What sequence? The only sequence here is the order of implemented interfaces. Are you suggesting First uses reflection to look at the order of implemented interfaces and returns the first one? That seems rather foolish, but it is certainly quite possible and should not be a compiler error. Or would it call GetEnumerator() and then get the first element? In which case, animals would need to be upcasted to the desired interface to bind to the appropriate GetEnumerator(). This has nothing to do with variance the current rules apply.

    Like Ben, without more information, it is difficult to judge. However, depending on the definition of First, either Giraffe or Turtle would be OK. I would not expect a warning, error, or exception.

  23. EricLippert says:

    First is an extension method which takes an IEnumerable<T>, calls GetEnumerator, and returns the first T in the sequence.  If there is no first T then it throws an exception.

  24. EricLippert says:

    The "First" is irrelevant. If it helps, suppose I had written

    foreach(Animal in animals) Console.WriteLine(animal.GetType().ToString());

    Should this code compile? Should it give a warning?  What output should it produce?

    Now suppose instead of

           IEnumerable<Animal> animals = new C();

    I’d said

           object x = new C();

           IEnumerable<Animal> animals = (IEnumerable<Animal>)x;

    foreach(Animal in animals) Console.WriteLine(animal.GetType().ToString());

    Should this code compile? Should it give a warning?  What output should it produce?

  25. Ben says:

    Considering Eric’s clarification and new example, error for both cases. More specifically, CS1640 seems appropriate. Why? Because it is the same issue as in existing implementations and has nothing to do with variance. Don’t over think it. The only variance issue here is the acceptability of assigning C to to IEnumerable<Animal>.

  26. EricLippert says:

    How would the compiler know to give an error for the second case? The conversion from C to object is unambiguous, as is the conversion from object to IE<Animal>.

  27. Ben says:

    Eric,

    1: object x = new C();

    2: IEnumerable<Animal> animals = (IEnumerable<Animal>)x;

    3: foreach(Animal in animals) Console.WriteLine(animal.GetType().ToString());

    Lines 1 and 2 are not an error.

    However, upon reaching line 3 (foreach), animals, which points to a C instance, implements IEnumerable<Giraffe> and IEnumerable<Turtle>, the same interface multiple times and should thus produce CS1640.

  28. Peter Ritchie says:

    Ben, because you’re converting to the IEnumerable<Animal> type first, the compiler will know which GetEnumerator to use.  The same happens in C# now:

    public class C : System.Collections.IEnumerable, IEnumerable<int>, IEnumerable<string>

    {

       IEnumerator<int> IEnumerable<int>.GetEnumerator()

       {

           yield break;

       }

       IEnumerator<string> IEnumerable<string>.GetEnumerator()

       {

           yield break;

       }

       System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

       {

           return (System.Collections.IEnumerator)((IEnumerable<string>)this).GetEnumerator();

       }

    }

    public class Test

    {

       public static int Main()

       {

           IEnumerable<int> e = new C();

           foreach (int i in e){}    // no error CS1640

           return 1;

       }

    }

  29. Ben says:

    Peter Ritchie,

    The difference though is that in your example, C does in fact implement IEnumerable<int>. Thus, it knows exactly which one to bind to. Changing e to C e = new C() will of course produce CS1640.

    > because you’re converting to the IEnumerable<Animal> type first, the compiler will know which

    > GetEnumerator to use.

    It does? And which one would that be? There is no IEnumerable<Animal>.GetEnumerator() that returns an IEnumerator<Animal>. It can only return an IEnumerator<Giraffe> or IEnumerator<Turtle>, both of which may be treated as an IEnumerator<Animal> and thus the ambiguituy: CS1640.

  30. Peter Ritchie says:

    Ben, the example code was pulled from the documentation for CS1640 and slightly modified.  This is the original code:

    public class C : IEnumerable, IEnumerable<int>, IEnumerable<string>

    {

       IEnumerator<int> IEnumerable<int>.GetEnumerator()

       {

           yield break;

       }

       IEnumerator<string> IEnumerable<string>.GetEnumerator()

       {

           yield break;

       }

       IEnumerator IEnumerable.GetEnumerator()

       {

           return (IEnumerator)((IEnumerable<string>)this).GetEnumerator();

       }

    }

    public class Test

    {

       public static int Main()

       {

           foreach (int i in new C()){}    // CS1640

           // Try specifing the type of IEnumerable<T>

           // foreach (int i in (IEnumerable<int>)new C()){}

           return 1;

       }

    }

    In Eric’s example, if he first converts to a IEnumerable<Animal> object, that object must have an IEnumerable<Animal> GetEnumerator() method so foreach knows exactly what GetEnumerator() to use, just as it knows in the IEnumerable<int>/IEnumerable<string> example because I forced it to use the IEnumerable<int> interface.  If you don’t tell it what type of IEnumerable<T> to use, yes, the compiler thankfully won’t make the decision for you and spits out CS1640; but that’s not the case the Eric presented.

  31. EricLippert says:

    > There is no IEnumerable<Animal>.GetEnumerator() that returns an IEnumerator<Animal>. It can only return an IEnumerator<Giraffe> or IEnumerator<Turtle>, both of which may be treated as an IEnumerator<Animal> and thus the ambiguituy: CS1640.

    OK, but how does the _compiler_ know that?  Suppose I split the code into three assemblies:

    assembly #1:

    public class D { public static void M(IEnumerable<Animal> animals) { foreach(Animal a in ….

    assembly #2:

    public class E { public static void N(object x) { D.M((IEnumerable<Animal>)x); } }

    assembly #3:

    public class C : IEnumerable<Giraffe>, IEnumerable<Turtle> { … }

    public class F { public static void P() { E.N(new C()); } }

    I compile assembly one, then assembly two, then assembly three.  In which one do I get a compilation error?

  32. James Birchall says:

    Peter Ritchie:

    I was thinking that notationally what you would get would be a set of operations common to every instance in the type return set.  So if we constrain the output to be of type either turtle or giraffe and there is a method signature called reproduce that returns something common to both turtles and giraffes (perhaps, something in the <giraffe | turtle> set) then it would be ok.  I was thinking of "weak duck typing", to put a made-up conceptual phrase on it.  Ie//

    IEnumerable<Giraffe | Turtle> obj = new C();

    Giraffe g = c.reproduce();  // Failure.  Could be a Turtle.

    Giraffe g2 = c.reproduce() as Giraffe;  // Sucess.  Turtle objects will be cast to NULL.

    Turtle t = c.reproduce(); // Failure.  Could be a Giraffe.

    Lion l = c.reproduce();  // Failure.  Not in the set of Turtle | Giraffe.

    <Giraffe | Turtle> gt = c.reproduce();  // Sucess.

    Animal a = c.reproduce();//  Sucess.

    You’d have to check operations on that derived code later on to tell whether it was asked to perform any operations that are invalid on the combined set.  Ie//

    gt.LayEggs();  // Failure.  Giraffe does not implement a "Lay Eggs" method.

    gt.RunAwayFromPredator(Predator p);  // Success.  Both Turtle and Giraffe implement the run away method.

    Another way to think of it would be automatic base-classing, where you’d get a duck-typed base class from the compiler that contained the set of operations common to both giraffes and turtles.

    And remember, the underlying object would still be either a giraffe or a turtle, with the associated memory layout.  All you’re doing notationally in my proposal is allowing the compiler to accept either one where it makes sense.  The compiler would still have to keep the set of types around and resolve whether any operations on the "multi-typed" type.

  33. Ben says:

    > OK, but how does the _compiler_ know that?

    Is there no way for the compiler to determine the exact type that something points at?

    If not, then it may need to be a run-time error.

    However, before that, in that case, it would fail when it looks for and can not find IEnumerator<Animal> IEnumerable<Animal>.GetEnumerator() in animals (of type IEnumerable<Animal>, but really pointing to C). That fact that C can also provide a IEnumerator<Giraffe> and IEnumerator<Turtle> which could pass as an IEnumerator<Animal> is immaterial if the compiler can not determine that animals is really an instance of C. Again, this would be a compile-time error, but not due to variance but rather because the compile can not determine that animals (IEnumerator<Animal>) is really a C instance.

  34. Luke says:

    It would be a terrible idea for C# to depend on the order of interfaces in the "inheritance list".  This would be like depending on the order attributes are applied.  I would be shocked if it weren’t a rule for C# design to avoid making ordering in such cases matter.  There’s just too much room for error.

    The type-casting logic needs to take into account the diamond problem and raise a compile-time error when possible, otherwise a run-time exception.  I see no reasonable way for the compiler to know how to choose IEnumerable<Turtle> over IEnumerable<Giraffe>.  If the author of the class wants to support IEnumerable<Animal>, he/she should implement it (I would say explicitly, but that has another meaning in C#).

    Now, would it be reasonable to produce a compilation warning about possible diamond hierarchies?

    Eric: your blog comments desperately needs a way to render code in <code>/<pre> tags (for inline/multiline code).  :-p

  35. Gilles Michard says:

    The problem here is not in covariance

    With present compiler, this code produces this compile error:

    ‘Test.C’ does not implement interface member ‘System.Collections.IEnumerable.GetEnumerator()’

    If I have to implement GetEnumerator in anyway, I would expect that all three versions of GetEnumerator would result in enumerating the same values.

    So these values must be of both type Giraffe and Turtle:

           System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

           {

               yield break ;

           }

    It makes no sense to implement this interface more than once.

    foreach (var x in new C()) : how would you infer the type of Animals? the 3.0 compiler produces:

    foreach statement cannot operate on variables of type ‘Test.C’ because it implements multiple instantiations of ‘System.Collections.Generic.IEnumerable<T>’; try casting to a specific interface instantiation

    So I expect that it should not be allowed to implement a generic interface more than once.

    It would be a breaking change, but it would only impact very badly written programs.

  36. EricLippert says:

    Yes, the problem here IS in covariance. I picked IEnumerable<T> as just one example, but you are concentrating solely on the semantics of IEnumerable.  How about if I pick some completely different interface:

    interface IFoo<+T> { T Get { get; } }

    class Bar : IFoo<Giraffe>, IFoo<Turtle> {

     IFoo<Giraffe>.Get{ get { return new Giraffe(); } }

     IFoo<Turtle>.Get{ get { return new Turtle(); } }

    }

    IFoo<Animal> f = new Bar();

    Console.WriteLine(f.Get().GetType().ToString());

    No IEnumerables at all. What should this do?

  37. EricLippert says:

    Why is implementing the same interface instantiated with two different sets of type arguments a bad programming practice?  If a Frob can be compared to a Frib or a Frub then why shouldn’t class Frob : IComparable<Frib>, IComparable<Frub> be perfectly legal?

  38. Aaron Hope says:

    Speaking as a novice…

    The real question is about the nature of covariant interfaces: does the object bear the burden of providing the full spectrum or are an object’s interface definitions pulling double-duty?  If the invariance is little more than shorthand for additional interface implementation requirements, then the class has to disambiguate IFoo<Animal> just to compile.  If the interface itself is making the guarantees, then Bar has two distinct IFoo<Animal> implementations, but neither should be directly accessible.

    In the former case, it’s Bar’s job to provide an unambiguous IFoo<Animal> implementation.  Unless the covariance guarantee is simply weak and translates to a runtime exception.

    In the latter case, it’s the runtime’s job to find the best match and detect ambiguities:

    IFoo<Animal> f = new Bar(); // runtime error

    IFoo<Mammal> m = new Bar();  // Get returns an Animal which is a Giraffe

    IFoo<Animal> g = new Bar() as IFoo<Giraffe>; // Get returns an Animal which is a Giraffe

    IFoo<Animal> t = new Bar() as IFoo<Turtle>;  // Get returns an Animal which is a Turtle

  39. DrPizza says:

    "IFoo<Animal> f = new Bar();

    Console.WriteLine(f.Get().GetType().ToString());

    No IEnumerables at all. What should this do?

    "

    The first line is statically ambiguous, with two legal-but-different conversions to IFoo<Animal> (you can go via IFoo<Giraffe> and via IFoo<Turtle>, with no way to choose).  Static ambiguity should (obviously) yield a compile-time error.  To resolve it you should have to choose a path, i.e. IFoo<Animal> f = (IFoo<Giraffe>)(new Bar()); to enumerate in the Giraffe style or IFoo<Animal> f = (IFoo<Turtle>)(new Bar()); to enumerate in the Turtle style.

    For the similar but different situation:

    "

    object o = new Bar();

    IFoo<Animal> f = (IFoo<Animal>)o;

    Console.WriteLine(f.Get().GetType().ToString());"

    Unfortunately we can’t make the first line ambiguous (there’s only one path back to object bceause there’s no multiple inheritance–if we had instead multiply inherited ABCs (which would extend object) instead of interfaces then the static ambiguity would return and be detectable).  So instead we make the second line ambiguous, at runtime.  It should throw an exception (AmbiguousCastException, say) that is extended from (but different to) InvalidCastException.

  40. Abraham Pinzur says:

    Eric,

    It appears that the very first line is itself inherently ambiguous. (That’s your point – right?) IEnumerable<Giraffe> implies one implicit conversion to IEnumerable<Animal>, and IEnumerable<Turtle> implies another (very different) implicit conversion to IEnumerable<Animal>. Thus, the definition of class C itself should result in a compile-time error, unless the developer provides some form of disambiguation.

    So, why not simply require class C to also implement IEnumerable<Animal> directly?

  41. I agree with the first answer by Stuart Ballard, and I would add that

    IEnumerable<Animal> a1 = (IEnumerable<Giraffe>) new C();

    IEnumerable<Animal> a2 = (IEnumerable<Turtle>) new C();

    Console.WriteLine(a1.First().GetType().ToString()); // Giraffe

    Console.WriteLine(a2.First().GetType().ToString()); // Turtle

    is workable, and appears to provide a sufficient means of disambiguation. Therefore, it shouldn’t be mandatory that C implement IEnumerable<Animal>.

    It appears to me that making generic IEnumerable covariant is not really a breaking change because code that tried to convert C to IEnumerable<Animal> was already illegal. OTOH, once IEnumerable becomes covariant, adding a new implementation of IEnumerable<T> (for a specific T) to almost any class will typically break existing code that performs a conversion to IEnumerable<object>.

    I wonder how C# and the CLR would handle code like the following–now and after adding variance:

    class Foo<T> : IEnumerable<string>, IEnumerable<T> where … { … } // um, T could be set to string

    class Foo<S,T> : IEnumerable<S>, IEnumerable<T> where … { … } // can this even be legal?

  42. Douglas McClean says:

    My gut says that it is a compile time error, not (as some above seem to feel?) in converting C to IEnumerable<Animal>, but in defining C to implement two interfaces related by a covariance conversion.

  43. So nicely step by step blogged by Eric Lippert for &quot;Covariance and Contravariance&quot; as &quot;Fabulous