Why does the C# compiler complain that "types may unify" when they derive from different base classes?

This is a consequence of section 13.4.2 of the C# 4 specification, which states:

If any possible constructed type created from C would, after type arguments are substituted into L, cause two interfaces in L to be identical, then the declaration of C is invalid. Constraint declarations are not considered when determining all possible constructed types.

Note that second sentence there.

It is therefore not a bug in the compiler; the compiler is correct. One might argue that it is a flaw in the language specification.

Generally speaking, constraints are ignored in almost every situation in which a fact must be deduced about a generic type. Constraints are mostly used to determine the effective base class of a generic type parameter, and little else.

Unfortunately, that sometimes leads to situations where the language is unnecessarily strict, as you have discovered.


It is in general a bad code smell to implement "the same" interface twice, in some way distinguished only by generic type arguments. It is bizarre, for example, to have class C : IEnumerable<Turtle>, IEnumerable<Giraffe> -- what is C that it is both a sequence of turtles, and a sequence of giraffes, at the same time? Can you describe the actual thing you're trying to do here? There might be a better pattern to solve the real problem.


If in fact your interface is exactly as you describe:

interface IFoo<T>
{
    void Handle(T t);
}

Then multiple inheritance of the interface presents another problem. You might reasonably decide to make this interface contravariant:

interface IFoo<in T>
{
    void Handle(T t);
}

Now suppose you have

interface IABC {}
interface IDEF {}
interface IABCDEF : IABC, IDEF {}

And

class Danger : IFoo<IABC>, IFoo<IDEF>
{
    void IFoo<IABC>.Handle(IABC x) {}
    void IFoo<IDEF>.Handle(IDEF x) {}
}

And now things get really crazy...

IFoo<IABCDEF> crazy = new Danger();
crazy.Handle(null);

Which implementation of Handle gets called???

See this article and the comments for more thoughts on this issue:

http://blogs.msdn.com/b/ericlippert/archive/2007/11/09/covariance-and-contravariance-in-c-part-ten-dealing-with-ambiguity.aspx


Apparently it was by design as discussed at Microsoft Connect:

  • Allow to implement same generic interface for more that one type parameter in generic class under some conditions

And the workaround is, define another interface as:

public interface IIFoo<T> : IFoo<T>
{
}

Then implement this instead as:

public class MyFoo<TA> : IIFoo<TA>, IFoo<B>
    where TA : A
{
    public void Handle(TA a) { }
    public void Handle(B b) { }
}

It now compiles fine, by mono.


You can sneak it under the radar if you put one interface on a base class.

public interface IFoo<T> {
}

public class Foo<T> : IFoo<T>
{
}

public class Foo<T1, T2> : Foo<T1>, IFoo<T2>
{
}

I suspect this works because if the types do "unify" it is clear the derived class's implementation wins.

Tags:

C#

Generics