Why does the C# compiler complain that “types may

2019-01-10 07:43发布

问题:

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?

回答1:

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



回答2:

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.



回答3:

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 { }


回答4:

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 }
    }

}


回答5:

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) { }
}


回答6:

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) { }
}


回答7:

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>
{
}


标签: c# generics