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.