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