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?
The letter c was applied by French orthographists in the 12th century to represent the sound ts in English, and this sound developed into the simpler sibilant s.
C, or c, is the third letter in the English and ISO basic Latin alphabets. Its name in English is cee (pronounced /ˈsiː/), plural cees. This article contains phonetic transcriptions in the International Phonetic Alphabet (IPA).
History. This alternation is caused by a historical palatalization of /k/ which took place in Late Latin, and led to a change in the pronunciation of the sound [k] before the front vowels [e] and [i].
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With