I recently wrote this and was surprised that it compiles:
public class MyGeneric<U, V> {
MyGeneric(U u) { ... }
MyGeneric(V v) { ... }
public void Add(U u, V v) { ... }
public void Add(V v, U u) { ... }
}
If I use this class as follows, I get an "Ambiguous constructor reference" and an "Ambiguous invocation" if I call Add.
var myVar = new MyGeneric<int, int>(new MyIntComparer());
Obviously, there's no ambiguity when I use int and double as generic types, except of course when I use both ints, which would also both assign to a double.
var myVar = new MyGeneric<int, double>(new MyIntComparer());
myVar.Add(3, 5);
So then I thought that the following was also allowed, but surprisingly I got an error. Why is the following not allowed to compile?
public interface IMyInterface<T, S> {
void Add(T t, S s);
}
public class MyGeneric<U, V> : IMyInterface<U, V>, IMyInterface<V, U> {
public MyGeneric(U u) { }
public MyGeneric(V v) { }
void IMyInterface<U, V>.Add(U u, V v) { ... }
void IMyInterface<V, U>.Add(V v, U u) { ... }
}
Regardless if I use implicit or explicit interface implementation, the compiler states that
'MyGeneric<U,V>' cannot implement both 'IMyInterface<U,V>' and 'IMyInterface<V,U>' because they may unify for some type parameter substitutions
And why is the first allowed to write?
1- Why is the following not allowed to compile ?
Part of the response is in that post : Why does the C# compiler complain that "types may unify" when they derive from different base classes?
The section 13.4.2 of the C# 4 specification states:
The interfaces implemented by a generic type declaration must remain unique for all possible constructed types. Without this rule, it would be impossible to determine the correct method to call for certain constructed types.
2- And why is the first allowed to write?
The compiler perform the generic type check at compile time, the section 7.4.3.5 of the C# 4 specification states:
While signatures as declared must be unique, it is possible that substitution of type arguments results in identical signatures. In such cases, the tie-breaking rules of overload resolution above will pick the most specific member. The following examples show overloads that are valid and invalid according to this rule:
interface I1<T> {...}
interface I2<T> {...}
class G1<U>
{
int F1(U u); // Overload resulotion for G<int>.F1
int F1(int i); // will pick non-generic
void F2(I1<U> a); // Valid overload
void F2(I2<U> a);
}
class G2<U,V>
{
void F3(U u, V v); // Valid, but overload resolution for
void F3(V v, U u); // G2<int,int>.F3 will fail
void F4(U u, I1<V> v); // Valid, but overload resolution for
void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail
void F5(U u1, I1<V> v2); // Valid overload
void F5(V v1, U u2);
void F6(ref U u); // valid overload
void F6(out V v);
}
It's part of the language specification, as explained in the accepted answer here :
Why does the C# compiler complain that "types may unify" when they derive from different base classes?
Section 13.4.2 of the C# 4 specification 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.
I guess the difference between your two example is that the second uses interfaces (checked for duplicates, per the language spec) but the first uses types (not checked for duplicates, despite potentially causing ambiguity as you have seen).
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