Обобщения в C # с несколькими универсальными типами приводят к разрешенной и запрещенной двусмысленности

Я недавно написал это и был удивлен, что он компилируется:

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

Если я использую этот класс следующим образом, я получаю «неоднозначную ссылку на конструктор» и «неоднозначный вызов», если вызываю Add.

var myVar = new MyGeneric<int, int>(new MyIntComparer());

Очевидно, нет двусмысленности, когда я использую int и double в качестве универсальных типов, за исключением, конечно, случая, когда я использую оба типа int, которые также будут назначены на double.

var myVar = new MyGeneric<int, double>(new MyIntComparer());
myVar.Add(3, 5);

Тогда я подумал, что можно и следующее, но, к моему удивлению, получил ошибку. Почему следующее не разрешено компилировать?

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

Независимо от того, использую ли я неявную или явную реализацию интерфейса, компилятор заявляет, что

'MyGeneric ‹U, V›' не может реализовать одновременно 'IMyInterface ‹U, V›' и 'IMyInterface ‹V, U›', потому что они могут унифицироваться для некоторых замен параметров типа.

А почему первым разрешено писать?


person Andreas    schedule 05.12.2012    source источник
comment
Хотя эти два сообщения в блоге говорят о случае, когда общий метод и неуниверсальный метод могут иметь одинаковую сигнатуру для аргументов определенного типа, они также могут применяться к вашему случаю с аргументами двух типов. (Ответ на вопрос, почему это разрешено, по сути, мы разрешили в C # 2.0, и сейчас уже слишком поздно его менять.)   -  person Rawling    schedule 05.12.2012
comment
Спасибо за эти ссылки, это объяснение реализации компилятора, которое я искал   -  person AlexH    schedule 05.12.2012
comment
@Rawling, спасибо за интересную ссылку.   -  person Andreas    schedule 05.12.2012


Ответы (2)


1- Почему следующее не разрешено компилировать?

Часть ответа содержится в этом сообщении: Почему компилятор C # жалуется на то, что типы могут объединяться, если они являются производными от разных базовых классов?

В разделе 13.4.2 спецификации C # 4 говорится:

Интерфейсы, реализованные посредством объявления универсального типа, должны оставаться уникальными для всех возможных сконструированных типов. Без этого правила было бы невозможно определить правильный метод для вызова определенных сконструированных типов.

2- А почему первым разрешено писать?

Компилятор выполняет проверку универсального типа во время компиляции, в разделе 7.4.3.5 спецификации C # 4 указано:

Хотя объявленные подписи должны быть уникальными, возможно, что замена аргументов типа приведет к идентичным подписям. В таких случаях приведенные выше правила разрешения перегрузки будут выбирать наиболее конкретный член. В следующих примерах показаны допустимые и недопустимые перегрузки в соответствии с этим правилом:

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);
}
person AlexH    schedule 05.12.2012
comment
Спасибо за подробное объяснение. Верно, что создание MyGeneric ‹int, int› в основном реализует один и тот же интерфейс два раза, и поэтому он жалуется на подстановки параметров. - person Andreas; 05.12.2012

Это часть спецификации языка, как описано в принятом ответе здесь:

Почему компилятор C # жалуется, что типы могут объединяться, когда они происходят от разных базовых классов?

В разделе 13.4.2 спецификации C # 4 говорится:

Если любой возможный сконструированный тип, созданный из C, после замены аргументов типа в L приведет к тому, что два интерфейса в L станут идентичными, то объявление C недействительно. Объявления ограничений не учитываются при определении всех возможных сконструированных типов.

Я предполагаю, что разница между вашими двумя примерами заключается в том, что второй использует интерфейсы (проверяется на дубликаты в соответствии со спецификацией языка), но первый использует типы (не проверяется на дубликаты, несмотря на то, что потенциально вызывает двусмысленность, как вы видели).

person Graham Griffiths    schedule 05.12.2012
comment
Спасибо, да, это правда, это в основном происходит из одного и того же интерфейса два раза. - person Andreas; 05.12.2012