6

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?

Andreas
  • 6,447
  • 2
  • 34
  • 46
  • 2
    Although [these](http://blogs.msdn.com/b/ericlippert/archive/2006/04/05/odious-ambiguous-overloads-part-one.aspx) [two](http://blogs.msdn.com/b/ericlippert/archive/2006/04/06/odious-ambiguous-overloads-part-two.aspx) blog posts are talking about the case where a generic method and a non-generic method could end up with the same signature for certain type arguments, they might also apply to your two-type-argument case. (The answer given as to why this is allowed is essentially "We allowed it in C# 2.0 and it's too late to change it now".) – Rawling Dec 05 '12 at 08:42
  • Thanks for these links, it is the compiler's implementation explanation I was looking for – AlexH Dec 05 '12 at 09:31
  • @Rawling, thanks for the interesting link. – Andreas Dec 05 '12 at 10:17
  • possible duplicate of [Why does the C# compiler complain that "types may unify" when they derive from different base classes?](http://stackoverflow.com/questions/7664790/why-does-the-c-sharp-compiler-complain-that-types-may-unify-when-they-derive-f) – nawfal May 25 '13 at 09:14

2 Answers2

4

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);
}
Community
  • 1
  • 1
AlexH
  • 2,650
  • 1
  • 27
  • 35
  • Thanks for the detailed explanation. It's true that creating MyGeneric would basically implement the same interface two times and that's why it complains about parameter substitutions. – Andreas Dec 05 '12 at 10:21
1

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).

Community
  • 1
  • 1
Graham Griffiths
  • 2,196
  • 1
  • 12
  • 15