Why does the C # compiler complain that “types can be combined” when they come from different base classes?

My current non-compiling code is like 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 it, citing the following rule / error:

'MyProject.MyFoo <TA>' cannot implement both parameters of "MyProject.IFoo <TA>"; and 'MyProject.IFoo <MyProject.B>' because they can be combined for some parameter substitutions of type

I understand what this error means; if TA can be anything at all, then it can technically also be B , which introduces ambiguity regarding two different Handle implementations.

But TA cannot be anything. Based on the type hierarchy, TA cannot be B - at least I don't think this is possible. TA must be derived from A , which is not derived from B and obviously there is no inheritance of the plural class in C # /. NET

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

So why am I getting this error? Is this a mistake, or the compiler’s non-intelligence at all, or is there something else that I’m missing?

Is there any workaround or will I just have to MyFoo common MyFoo class as a separate non-generic class for each possible derived TA type?

+61
generics c #
Oct 05 2018-11-11T00:
source share
6 answers

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

If any possible constructed type created from C, after the type arguments are replaced by L, causes the identity of the two interfaces in L, then the declaration of C is invalid. Constraint declarations are not taken into account when defining all possible constructed types.

Please note that the second sentence is there.

Therefore, this is not a compiler error; the compiler is right. It can be argued that this is a flaw in the language specification.

Generally speaking, restrictions are ignored in almost every situation in which a fact about a generic type must be inferred. Constraints are mainly used to define an effective base class of a type parameter parameter and a little different.

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




In general, a bad code smell implements the "same" interface twice, in a sense differing only in arguments of a general type. 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 what you are trying to do here? Perhaps there will be a better model to solve the real problem.




If in fact your interface is exactly the same as you describe:

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

Then multiple interface inheritance is another problem. You can 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 everything is getting really crazy ...

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

What implementation of Handle is called ???

See this article and 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

+38
Oct 05 '11 at 17:19
source share

Apparently, this was by design, as discussed at Microsoft Connect:

And a workaround is to define another interface as:

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

Then implement this instead:

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

Now it compiles fine, mono .

+8
Oct 05 '11 at 17:01
source share

See my answer basically to the same question: https://stackoverflow.com/a/318618/

To some extent this can be done! I use the differentiation method instead of classifiers that restrict types.

It does not merge, in fact it could be better than if it happened because you could separate the individual interfaces from each other.

See my post here, with a fully working example in a different context. stack overflow

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

Then, when you use it twice, you pass “First” for first use, and “Second” for second use

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

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

where First and Second are trivial:

 interface First { } interface Second { } 
+2
Sep 11 '12 at 2:01
source share

I know that some time has passed since the stream was published, but for those who came to this stream through the search engine for help. Note that “Base” means the base class for TA and B 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 } } } 
+1
May 10 '16 at 2:38
source share

Now guess ...

Is it impossible to declare A, B and C in external assemblies where the type hierarchy can change after compiling MyFoo <T>, which will lead to chaos in the world?

An easy workaround is to simply implement Handle (A) instead of Handle (TA) (and use IFoo <A> instead of IFoo <TA>). You cannot do more with Handle (TA) than access methods from A (due to A: TA restriction) in any case.

 public class MyFoo : IFoo<A>, IFoo<B> { public void Handle(A a) { } public void Handle(B b) { } } 
0
05 Oct '11 at 17:03
source share

Hmm, how 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) { } } 
0
05 Oct '11 at 17:11
source share



All Articles