Подтвердить что ты не робот

Интерфейс, реализованный дважды, "типы могут объединяться"; почему это обходное решение работает?

Я столкнулся с ошибкой компилятора при попытке реализовать интерфейс дважды для одного и того же класса:

public class Mapper<T1, T2> : IMapper<T1, T2>, IMapper<T2, T1>
{
   /* implementation for IMapper<T1, T2> here.  */

   /* implementation for IMapper<T2, T1> here.  */
}

Ошибка:

"Mapper" не может реализовать как "IMapper", так и "IMapper", поскольку они могут объединяться для некоторых подстановок параметров типа.

Почему это обходное решение работает? Мне интересно, разрешил ли я проблему или просто обманул компилятор.

public class Mapper<T1, T2> : MapperBase<T1, T2>, IMapper<T1, T2>
{
    /* implementation for IMapper<T1, T2> here. */
}

public class MapperBase<T1, T2> : IMapper<T2, T1>
{
    /* implementation for IMapper<T2, T1> here. */
}

EDIT. Я обновил MyClass, MyClassBase и IMyInterface до Mapper, MapperBase и IMapper, чтобы представить более реальный сценарий, где этот вопрос может присутствовать.

4b9b3361

Ответ 1

Рассмотрим эту реализацию:

public class MyClass<T1, T2> : IMyInterface<T1, T2>, IMyInterface<T2, T1>
{
   /* implementation for IMyInterface<T1, T2> here.  */

   /* implementation for IMyInterface<T2, T1> here.  */
}

Что реализует MyClass<int, int>? Он реализует IMyInterface<int, int> дважды, потому что IMyInterface<T1, T2> и IMyInterface<T2, T1> объединяются, когда T1 и T2 равны. Поэтому использование обоих IMyInterface<T1, T2> и IMyInterface<T2, T1> в одном классе запрещено. Те же рассуждения применимы, если вы попытались реализовать, например, IMyInterface<int, T1> и IMyInterface<T2, double>: выражения типов объединяются для T1 = double, T2 = int.

Рассмотрим эту реализацию:

public class MyClass<T1, T2> : MyClassBase<T1, T2>, IMyInterface<T1, T2>
{
    /* implementation for IMyInterface<T1, T2> here. */
}

public class MyClassBase<T1, T2> : IMyInterface<T2, T1>
{
    /* implementation for IMyInterface<T2, T1> here. */
}

Вы сделали приоритет на IMyInterface<T1, T2> над IMyInterface<T2, T1>. В случае, если T1 и T2 равны, и у вас есть экземпляр MyClass<T1, T2>, будет выбрана реализация IMyInterface<T1, T2>. Если у вас есть экземпляр MyBaseClass<T1, T2>, будет выбрана реализация IMyInterface<T2, T1>.

Вот игрушечная программа, которая показывает вам поведение. В частности, обратите внимание на поведение a_as_i.M(0, 1) и a_as_b.M(0, 1). Если вы должны явно реализовать I<T2, T1> на B<T1, T2> (путем префикса имени метода с помощью I<T2, T1>.), было бы невозможно вызвать его с использованием синтаксиса времени компиляции. Отражение будет необходимо.

interface I<T1, T2>
{
    void M(T1 x, T2 y);
}

class A<T1, T2> : B<T1, T2>, I<T1, T2>
{
    public void M(T1 x, T2 y)
    {
        Console.WriteLine("A: M({0}, {1})", x, y);
    }
}

class B<T1, T2> : I<T2, T1>
{
    public void M(T2 x, T1 y)
    {
        Console.WriteLine("B: M({0}, {1})", x, y);
    }
}

class Program
{
    static void Main(string[] args)
    {
        //Outputs "A: M(0, 1)"
        var a = new A<int, int>();
        a.M(0, 1);

        //Outputs "B: M(0, 1)"
        var b = new B<int, int>();
        b.M(0, 1);

        //Outputs "A: M(0, 1)" because I<T1, T2>
        //takes precedence over I<T2, T1>
        var a_as_i = a as I<int, int>;
        a_as_i.M(0, 1);

        //Outputs "B: M(0, 1)" despite being called on an instance of A
        var a_as_b = a as B<int, int>;
        a_as_b.M(0, 1);

        Console.ReadLine();
    }
}

Ответ 2

Вы не обманули компилятор, вы сделали это так, чтобы у вас не было определений конкурирующих функций. Предположим, что ваш интерфейс имеет функцию string Convert(T1 t1, T2 t2). С вашим первым (незаконным) кодом, если вы сделали MyClass<string, string>, у вас было бы 2 экземпляра одной и той же функции. С вашим базовым классом эти 2 экземпляра будут находиться в MyClassBase и MyClass, поэтому один из MyClass будет СКРЫВАТЬ другой, а не конфликтует с этим. Независимо от того, работает это или нет, я полагаю.

Ответ 3

Я считаю, что проблема связана с неспособностью компилятора выявить, какой из реализованных методов следует вызывать, если один из типов T1 или T2 является потомком другого (или является одним и тем же типом).
Представьте, что делать, если вы создаете экземпляр класса MyClass<int, int>.