Given this "IHandle" interface and two classes to be handled:
interface IHandle<T>
{
void Handle(T m);
}
class M1
{
public int Id;
}
class MReset
{
}
I want to create a generic base that takes care of "resetting" as well as managing M1 instances:
class HandlerBase<T> :
IHandle<MReset>,
IHandle<T> where T : M1
{
protected int Count;
void IHandle<T>.Handle(T m)
{
++Count;
Console.WriteLine("{0}: Count = {0}", m.Id, Count);
}
void IHandle<MReset>.Handle(MReset m)
{
Count = 0;
}
}
This does not compile since the compiler believes T could be "MReset" so it outputs:
error CS0695: 'HandlerBase' cannot implement both 'IHandle' and 'IHandle' because they may unify for some type parameter substitutions
That in itself is slightly odd since I cannot see how T could possibly be of type MReset since it must be of type M1. But okay, I can accept that the compiler is smarter than me :-)
Edit: The compiler is not smarter than me :-) According to a comment on Why does this result in CS0695? we have "Constraint declarations are not considered when determining all possible constructed types".
Now I swap the interface declarations:
class HandlerBase<T> :
IHandle<T> where T : M1,
IHandle<MReset>
{
... same as before ..
}
And suddenly I get a different error message stating that I cannot implement IHandle.Handle(MReset m) since the class declaration does not state that it is implementing that interface:
error CS0540: 'HandlerBase.IHandle<...>.Handle(MReset)': containing type does not implement interface 'IHandle'
Question: why does the order of declarations make such a difference? What is going wrong in the second example?
In the end it turns out that there is a solution:
class HandlerBase :
IHandle<MReset>
{
protected int Count;
void IHandle<MReset>.Handle(MReset m)
{
Count = 0;
}
}
class Handler<T> : HandlerBase,
IHandle<T> where T : M1
{
void IHandle<T>.Handle(T m)
{
++Count;
Console.WriteLine("{0}: Count = {0}", m.Id, Count);
}
}
But the solution only works if HandlerBase
implements IHandle<MReset>
- not if the generic interface IHandle<T>
is implemented in HandlerBase
first. Why?
Edit: Implementing IHandle<T>
in HandlerBase
does work (and if I had shown the code someone might have seen it). This works:
class HandlerBase<T> :
IHandle<T> where T : M1
{
protected int Count;
void IHandle<T>.Handle(T m)
{
++Count;
Console.WriteLine("Type = {0}, Id = {1}, Count = {2}", GetType(), m.Id, Count);
}
}
class Handler<T> : HandlerBase<T>,
IHandle<MReset>
where T : M1
{
void IHandle<MReset>.Handle(MReset m)
{
Count = 0;
Console.WriteLine("RESET");
}
}
Unfortunately my second class declaration was this:
class Handler<T> : HandlerBase<T> where T : M1,
IHandle<MReset>
{
void IHandle<MReset>.Handle(MReset m)
{
Count = 0;
Console.WriteLine("RESET");
}
}
Notice the subtle difference in the location of where T : M1
:-) The last example declares that T must implement IHandle<MReset>
(in addition to M1
). Duh!
Problem solved - I found the subtle difference. When the order of declarations is swapped I should not move where T : M1
since the IHandle<MReset>
constraint then ends up being applied to T instead of the class declaration:
class HandlerBase<T> :
IHandle<T> where T : M1,
IHandle<MReset>
{
... same as before ..
}
The correct re-ordering should have been:
class HandlerBase<T> :
IHandle<T>,
IHandle<MReset>
where T : M1
{
... same as before ..
}
@Siram pointed out that the uniqueness problem (but not the order aspect) has been answered in Why does this result in CS0695?:
The C# language spec (https://www.microsoft.com/en-us/download/confirmation.aspx?id=7029) discusses the "Uniqueness of implemented interfaces" in 13.4.2.: "The interfaces implemented by a generic type declaration must remain unique for all possible constructed types." And later, when describing the details of the check: "Constraint declarations are not considered when determining all possible constructed types."
Why that is so I am not sure; perhaps one can construct nested or chained constraints which make it impossible for the compiler to prove uniqueness, or not all constraints can be communicated via assemblies (which, I think, would be necessary for the general language rule).
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