81

My current non-compiling code is similar to this:

public abstract class A { }

public class B { }

public class C : A { }

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

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

The C# compiler refuses to compile this, citing the following rule/error:

'MyProject.MyFoo<TA>' cannot implement both 'MyProject.IFoo<TA>' and 'MyProject.IFoo<MyProject.B>' because they may unify for some type parameter substitutions

I understand what this error means; if TA could be anything at all then it could technically also be a B which would introduce ambiguity over the two different Handle implementations.

But TA can't be anything. Based on the type hierarchy, TA can't be a B - at least, I don't think it can. TA must derive from A, which does not derive from B, and obviously there's no multiple class inheritance in C#/.NET.

If I remove the generic parameter and replace TA with C, or even A, it compiles.

So why do I get this error? Is it a bug in or general un-intelligence of the compiler, or is there something else I'm missing?

Is there any workaround or am I just going to have to re-implement the MyFoo generic class as a separate non-generic class for every single possible TA derived type?

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • 2
    I think TItem should read TA, no? – Jeff Oct 05 '11 at 16:56
  • Its unlikely to be a bug in the compiler. To be fair the error message does use the words "may unify" my guess is that its because you use both interfaces. – Security Hound Oct 05 '11 at 17:00
  • Edit: Nevermind, I read that as B being a type parameter. What's stopping you from passing the same thing to type parameter `B` as you pass to `TA`? – Josh Oct 05 '11 at 17:00
  • 1
    @JoshEinstein: `B` is not a type parameter, it's an actual type. The only type parameter is `TA`. – Aaronaught Oct 05 '11 at 17:01
  • @Josh: Because TA has a constraint that it inherits from type A, which is never type B. MyFoo is not permitted. – Jeff Oct 05 '11 at 17:03
  • 1
    @Ramhound I don't see why it is "unlikely" to be a compiler bug. I can see no other explanation, really. It seems like an easy mistake to make and situation that doesn't come up very often. – kmkemp Oct 05 '11 at 17:04
  • If there was no generic constraint, the error would be accurate. But the "may unify" really isn't true, since the generic constraint exsits. It might not be a bug, but it does at least sound like the compiler isn't reading enough into it... – Jeff Oct 05 '11 at 17:06
  • Note that I did qualify "bug" with "...or general un-intelligence" - it may be simply an implementation quirk, an enforcement of this "rule" before the constraints are ever noticed, but of course I'd prefer not to speculate (for all I know it may be a subtle bug in *my* code). – Aaronaught Oct 05 '11 at 17:10

7 Answers7

54

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

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • `void IFoo.Handle(IABC x)` and I'd guess we are seeing internal implementation details peeking out? – asawyer Oct 05 '11 at 17:43
  • Would this line `IFoo crazy = new Danger();` compile? – Nawaz Oct 05 '11 at 17:44
  • 1
    @asawyer: Yep. It is implementation-defined which conversion the CLR takes in this case. And in fact, you can construct more complex situations in which it *is* defined by the CLI spec which one the implementation should take, and the CLR takes the "wrong" one. I am not sure if the CLR team and the CLI specification owners have resolved that dispute yet; my personal opinion is that the CLI rules actually give less good results than the CLR rules in the most likely cases. (Not that any of these cases are common; they are all pretty weird corner cases.) – Eric Lippert Oct 05 '11 at 17:48
  • @Nawaz Doesnt hurt to try it out. – asawyer Oct 05 '11 at 17:48
  • 3
    Definitely, it makes no sense when interfaces are the type parameters; I had incorrectly assumed that using concrete classes would work around the issue. As for the scenario, the class is a Saga, which *must* implement several message handlers (one for each message that's part of the saga). There are about 10 nearly-identical sagas whose only difference is the exact concrete type of one of the messages, but I can't have an abstract message handler, so I thought I'd attempt to use a generic base class and just use a bunch of stub classes; the only alternative is a lot of copy-and-pasting. – Aaronaught Oct 05 '11 at 18:50
  • 23
    @Eric: The situation of implementing the same generic interface with more than once is more likely if you consider an interface like `IComparable` or `IEquatable` rather than `IEnumerable`. It's quite plausible to have an object that can be compared to more than one kind of type ... in fact, I've run into the type unification issues several times for this exact case. – LBushkin Oct 05 '11 at 20:17
  • 5
    @Eric, LBushkin is correct. That *some* uses are non-sensical, does not imply *all* uses are non-sensical. This problem just bit me too, because I was implementing an abstract parsing interface IFoo which implements a piecewise IParseable, IParseable, ... with a set of extension methods defined on IParseable, but now that seems impossible. C#/CLR has many frustrating corner cases like this. – naasking Nov 12 '11 at 21:39
  • 1
    @EricLippert Am I correct in assuming that there has been added features to allow ambiguity in "certain" conditions? It seems you are now (.NET 4.5) allowed to express the above example, whereas a generic version is still not allowed, i.e. `class Danger : IFoo, IFoo {...}` is allowed, whereas `class Danger : IFoo, IFoo` Is still disallowed. It seems that disambiguation is done by a deterministic arbitration, where the first defined most specific implementation is used (where multiple are applicable)? – micdah Oct 22 '12 at 09:44
  • I wished C# could show an error when trying to realize a generic type, so that it could be used in a situation where generic parameters don't create interface collisions. The current implementation makes it impossible to implement `IEquatable` and `IEquatable`. – Miguel Angelo Apr 25 '16 at 15:43
  • "It is in general a bad code smell to implement "the same" interface twice" - this seems like an unreasonable claim: there are plenty of obvious situations when someone wants to do this. And there are plenty of times the code smell is in 3rd party code we have no control over (e.g. I had today to do this for a class that implements Foo and also Foo, due to some foolish code by a 3rd party giant corporation). Unless the language is going to 'police' all 3rd parties then its unfair to block people from solving problems. – Adam Sep 11 '22 at 22:45
8

Apparently it was by design as discussed at Microsoft Connect:

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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Upvoted for the Connect link; unfortunately, the workaround doesn't compile with the Microsoft compiler. – Aaronaught Oct 05 '11 at 19:00
  • @Aaronaught: Try making `IIFoo` an `abstract class` then. – Nawaz Oct 05 '11 at 19:02
  • That does compile, although it obviously prevents me from deriving from a different base class. I think I might actually be able to make this work, although it will mandate another intermediate (hackish/useless) level in the class hierarchy. – Aaronaught Oct 05 '11 at 19:12
  • This actually violates the spec if I'm not mistaken. The section that Eric quoted specifies that it shouldn't be possible, doesn't it? – configurator Oct 05 '11 at 20:15
  • @configurator: That's probably why it's illegal in the Microsoft compiler. Deriving from an abstract class which implements one of the interfaces skirts the issue because the wording in the spec only applies to type L - that is `MyFoo` and not its potential answer, `MyFooBase : IFoo`. Still ugly... – Aaronaught Oct 05 '11 at 21:21
  • @Aaronaught: When implementing via a base class, the answer is actually deterministic; it's like having `class A: IDisposable` and `class B: A, IDisposable`. Not the same thing as implementing via an interface. – configurator Oct 05 '11 at 21:23
  • @configurator: It's deterministic with interfaces too, as long as the constraint is there. You could just as easily introduce ambiguity with an abstract class if you aren't careful, i.e. a signature like `class A : List, IEnumerable` and then creating `A`; the behaviour just happens to be formally specified through member hiding rules. – Aaronaught Oct 05 '11 at 21:58
  • 2
    @Aaronaught: It's legal for a type to implement the same interface twice through its inheritance chain (for hiding, and to mitigate the brittle base class). But it's not legal for the same type to implement the same interface twice at the same place - because there's no 'better' one. – configurator Oct 06 '11 at 14:11
5

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.

Colin
  • 588
  • 6
  • 9
2

See my reply to basically the same question here: https://stackoverflow.com/a/12361409/471129

To some extent, this can be done! I use a differentiating method, instead of qualifier(s) limiting the types.

It does not unify, in fact it might be better than if it did because you can tease the separate interfaces apart.

See my post here, with a fully working example in another context. https://stackoverflow.com/a/12361409/471129

Basically, what you do is add another type parameter to IIndexer, so that it become IIndexer <TKey, TValue, TDifferentiator>.

Then you when you use it twice, you pass "First" to the 1st use, and "Second" for the 2nd use

So, class Test becomes: class Test<TKey, TValue> : IIndexer<TKey, TValue, First>, IIndexer<TValue, TKey, Second>

Thus, you can do new Test<int,int>()

where First, and Second are trivial:

interface First { }

interface Second { }
Community
  • 1
  • 1
Erik Eidt
  • 23,049
  • 2
  • 29
  • 53
0

I know it has been a while since the thread was posted but for those who come down to this thread via the search engine for help. Note that 'Base' stands for base class for TA and B in below.

public class MyFoo<TA> : IFoo<Base> where TA : Base where B : Base
{
    public void Handle(Base obj) 
    { 
       if(obj is TA) { // TA specific codes or calls }
       else if(obj is B) { // B specific codes or calls }
    }

}
Yohan Chung
  • 519
  • 1
  • 6
  • 15
0

Taking a guess now...

Couldn't A, B and C be declared in outside assemblies, where the type hierarchy may change after the compilation of MyFoo<T>, bringing havoc into the world?

The easy workaround is just to implement Handle(A) instead of Handle(TA) (and use IFoo<A> instead of IFoo<TA>). You cant do much more with Handle(TA) than access methods from A (due to the A : TA constraint) anyway.

public class MyFoo : IFoo<A>, IFoo<B> {
    public void Handle(A a) { }
    public void Handle(B b) { }
}
sisve
  • 19,501
  • 3
  • 53
  • 95
  • No to this >> `Couldn't A, B and C be declared in outside assemblies, where the type hierarchy may change after the compilation of MyFoo, bringing havoc into the world?`. – Nawaz Oct 05 '11 at 17:04
  • Changing the type hierarchy would invalidate just about *any* program; it doesn't make a whole lot of sense to me that the compiler would base its decisions on anything other than what the type hierarchy is right *now* (although, I guess it makes no less sense to me than the error itself at the moment...) As for the workaround, well, that will compile but it's no longer generic, so it doesn't really help much. – Aaronaught Oct 05 '11 at 17:08
0

Hmm, what about this:

public class MyFoo<TA> : IFoo<TA>, IFoo<B>
    where TA : A
{
    void IFoo<TA>.Handle(TA a) { }
    void IFoo<B>.Handle(B b) { }
}
luqui
  • 59,485
  • 12
  • 145
  • 204
  • 2
    No, the error is on the class itself; it doesn't depend on the way the interface is implemented. – Qwertie Feb 03 '12 at 19:13