Domanda

I've run into a compiler error when attempting to implement an interface twice for the same class like so:

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

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

The error:

'Mapper' cannot implement both 'IMapper' and 'IMapper' because they may unify for some type parameter substitutions.

Why does this workaround work? I'm wondering if I've solved the problem or just tricked the compiler.

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: I've updated MyClass, MyClassBase, and IMyInterface to Mapper, MapperBase, and IMapper to represent a more real-world scenario where this issue may present itself.

È stato utile?

Soluzione

Consider this implementation:

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

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

What does MyClass<int, int> implement? It implements IMyInterface<int, int> twice, because IMyInterface<T1, T2> and IMyInterface<T2, T1> unify when T1 and T2 are equal. That's why implementing both IMyInterface<T1, T2> and IMyInterface<T2, T1> on the same class is disallowed. The same reasoning would apply if you tried to implement, for example, IMyInterface<int, T1> and IMyInterface<T2, double>: the type expressions unify for T1 = double, T2 = int.

Consider this implementation:

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. */
}

What you've done is place a priority on IMyInterface<T1, T2> over IMyInterface<T2, T1>. In the event that T1 and T2 are equal and you have an instance of MyClass<T1, T2>, the IMyInterface<T1, T2> implementation will be selected. If you have an instance of MyBaseClass<T1, T2>, the IMyInterface<T2, T1> implementation will be selected.

Here's a toy program that shows you the behaviors. In particular notice the behavior of a_as_i.M(0, 1) and a_as_b.M(0, 1). If you were to implement I<T2, T1> explicitly on B<T1, T2> (by prefixing the method name with I<T2, T1>.), it would be impossible to call it using compile-time syntax. Reflection would be necessary.

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();
    }
}

Altri suggerimenti

You haven't tricked the compiler, you've made it so that you won't have competing function definitions. Assume your interface has the function string Convert(T1 t1, T2 t2). With your first (illegal) code, if you did MyClass<string, string>, you would have 2 instances of the same function. With your base class, those 2 instances will be in MyClassBase and MyClass, so the one in MyClass will HIDE the other one, instead of conflict with that. Whether that works or not is up to you, I suppose.

I believe the problem is caused by unability of the compiler to reveal which of the implemented methods should be invoked if one of the T1 or T2 types is descendant of another (or is the same type).
Imagine what should it do if you instantiate the class MyClass<int, int>.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top