实现通用接口时奇怪的 C# 行为
Odd C# behavior when implementing generic interface
鉴于此 "IHandle" 接口和两个 class 要处理的对象:
interface IHandle<T>
{
void Handle(T m);
}
class M1
{
public int Id;
}
class MReset
{
}
我想创建一个通用基础来处理 "resetting" 以及管理 M1 实例:
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;
}
}
这不会编译,因为编译器认为 T 可能是 "MReset",所以它输出:
error CS0695: 'HandlerBase' cannot implement both 'IHandle'
and 'IHandle' because they may unify for some type parameter
substitutions
这本身有点奇怪,因为我看不出 T 怎么可能是 MReset 类型,因为它必须是 M1 类型。但是好吧,我可以接受编译器比我聪明:-)
编辑:编译器并不比我聪明 :-) 根据对 Why does this result in CS0695? 的评论,我们有 "Constraint declarations are not considered when determining all possible constructed types".
现在我交换接口声明:
class HandlerBase<T> :
IHandle<T> where T : M1,
IHandle<MReset>
{
... same as before ..
}
突然我收到一条不同的错误消息,指出我无法实现 IHandle.Handle(MReset m) 因为 class 声明没有声明它正在实现该接口:
error CS0540: 'HandlerBase.IHandle<...>.Handle(MReset)': containing
type does not implement interface 'IHandle'
问题:为什么声明的顺序会造成如此大的差异?第二个例子出了什么问题?
最后发现有解决办法:
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);
}
}
但该解决方案仅在 HandlerBase
实现 IHandle<MReset>
时有效 - 如果通用接口 IHandle<T>
首先在 HandlerBase
中实现则无效。 为什么?
编辑:在HandlerBase
中实施IHandle<T>
是否有效(如果我向某人展示了代码可能看过)。这有效:
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");
}
}
不幸的是,我的第二个 class 声明是这样的:
class Handler<T> : HandlerBase<T> where T : M1,
IHandle<MReset>
{
void IHandle<MReset>.Handle(MReset m)
{
Count = 0;
Console.WriteLine("RESET");
}
}
请注意 where T : M1
位置的细微差别 :-) 最后一个示例声明 T 必须实现 IHandle<MReset>
(除了 M1
)。呸!
@Siram 指出唯一性问题(但不是顺序方面)已经在Why does this result in CS0695?:
中得到解答
C# 语言规范 (https://www.microsoft.com/en-us/download/confirmation.aspx?id=7029) 讨论了 13.4.2 中的 "Uniqueness of implemented interfaces":"The interfaces implemented by a generic type declaration must remain unique for all possible constructed types." 以及稍后在描述检查细节时: "Constraint declarations are not considered when determining all possible constructed types."
为什么会这样我不确定;也许可以构造嵌套或链式约束,这使得编译器无法证明唯一性,或者并非所有约束都可以通过程序集进行通信(我认为这对于通用语言规则是必需的)。
问题已解决 - 我发现了细微差别。当声明的顺序被交换时,我应该 而不是 移动 where T : M1
因为 IHandle<MReset>
约束最终被应用到 T 而不是 class 声明:
class HandlerBase<T> :
IHandle<T> where T : M1,
IHandle<MReset>
{
... same as before ..
}
正确的重新排序应该是:
class HandlerBase<T> :
IHandle<T>,
IHandle<MReset>
where T : M1
{
... same as before ..
}
鉴于此 "IHandle" 接口和两个 class 要处理的对象:
interface IHandle<T>
{
void Handle(T m);
}
class M1
{
public int Id;
}
class MReset
{
}
我想创建一个通用基础来处理 "resetting" 以及管理 M1 实例:
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;
}
}
这不会编译,因为编译器认为 T 可能是 "MReset",所以它输出:
error CS0695: 'HandlerBase' cannot implement both 'IHandle' and 'IHandle' because they may unify for some type parameter substitutions
这本身有点奇怪,因为我看不出 T 怎么可能是 MReset 类型,因为它必须是 M1 类型。但是好吧,我可以接受编译器比我聪明:-)
编辑:编译器并不比我聪明 :-) 根据对 Why does this result in CS0695? 的评论,我们有 "Constraint declarations are not considered when determining all possible constructed types".
现在我交换接口声明:
class HandlerBase<T> :
IHandle<T> where T : M1,
IHandle<MReset>
{
... same as before ..
}
突然我收到一条不同的错误消息,指出我无法实现 IHandle.Handle(MReset m) 因为 class 声明没有声明它正在实现该接口:
error CS0540: 'HandlerBase.IHandle<...>.Handle(MReset)': containing type does not implement interface 'IHandle'
问题:为什么声明的顺序会造成如此大的差异?第二个例子出了什么问题?
最后发现有解决办法:
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);
}
}
但该解决方案仅在 HandlerBase
实现 IHandle<MReset>
时有效 - 如果通用接口 IHandle<T>
首先在 HandlerBase
中实现则无效。 为什么?
编辑:在HandlerBase
中实施IHandle<T>
是否有效(如果我向某人展示了代码可能看过)。这有效:
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");
}
}
不幸的是,我的第二个 class 声明是这样的:
class Handler<T> : HandlerBase<T> where T : M1,
IHandle<MReset>
{
void IHandle<MReset>.Handle(MReset m)
{
Count = 0;
Console.WriteLine("RESET");
}
}
请注意 where T : M1
位置的细微差别 :-) 最后一个示例声明 T 必须实现 IHandle<MReset>
(除了 M1
)。呸!
@Siram 指出唯一性问题(但不是顺序方面)已经在Why does this result in CS0695?:
中得到解答C# 语言规范 (https://www.microsoft.com/en-us/download/confirmation.aspx?id=7029) 讨论了 13.4.2 中的 "Uniqueness of implemented interfaces":"The interfaces implemented by a generic type declaration must remain unique for all possible constructed types." 以及稍后在描述检查细节时: "Constraint declarations are not considered when determining all possible constructed types."
为什么会这样我不确定;也许可以构造嵌套或链式约束,这使得编译器无法证明唯一性,或者并非所有约束都可以通过程序集进行通信(我认为这对于通用语言规则是必需的)。
问题已解决 - 我发现了细微差别。当声明的顺序被交换时,我应该 而不是 移动 where T : M1
因为 IHandle<MReset>
约束最终被应用到 T 而不是 class 声明:
class HandlerBase<T> :
IHandle<T> where T : M1,
IHandle<MReset>
{
... same as before ..
}
正确的重新排序应该是:
class HandlerBase<T> :
IHandle<T>,
IHandle<MReset>
where T : M1
{
... same as before ..
}