实现通用接口时奇怪的 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 ..
}