【问题标题】:Odd C# behavior when implementing generic interface实现泛型接口时出现奇怪的 C# 行为
【发布时间】:2015-07-02 19:58:24
【问题描述】:

给定这个“IHandle”接口和两个要处理的类:

interface IHandle<T>
{
  void Handle(T m);
}


class M1
{
  public int Id;
}


class MReset
{
}

我想创建一个通用基础来处理“重置”以及管理 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”所以它输出:

错误 CS0695:“HandlerBase”无法同时实现“IHandle” 和“IHandle”,因为它们可能会针对某些类型参数统一 换人

这本身有点奇怪,因为我看不出 T 怎么可能是 MReset 类型,因为它必须是 M1 类型。不过好吧,我可以接受编译器比我聪明:-)

编辑:编译器并不比我聪明 :-) 根据Why does this result in CS0695? 上的评论,我们有“在确定所有可能的构造类型时不考虑约束声明”。

现在我交换接口声明:

class HandlerBase<T> :
  IHandle<T> where T : M1,
  IHandle<MReset>
{
  ... same as before ..
}

突然我收到一条不同的错误消息,指出我无法实现 IHandle.Handle(MReset m),因为类声明没有说明它正在实现该接口:

错误 CS0540: 'HandlerBase.IHandle<...>.Handle(MReset)': 包含 类型未实现接口“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&lt;MReset&gt; 时才有效 - 如果通用接口 IHandle&lt;T&gt; 首先在 HandlerBase 中实现则无效。 为什么

编辑:在HandlerBase 中实现IHandle&lt;T&gt; 确实 工作(如果我展示了代码,有人可能已经看到了)。这有效:

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 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&lt;MReset&gt;(除了M1)。呵呵!

【问题讨论】:

  • Related / Duplicate。我并没有因为这个问题“为什么声明的顺序会产生如此大的差异?”而关闭重复。那里没有回答。
  • 确实是相关链接。我刚刚在那里编辑了接受的答案,以使 C# 规范中的重要信息更加可见:“在确定所有可能的构造类型时不考虑约束声明。”

标签: c# generics inheritance interface


【解决方案1】:

问题解决了——我发现了细微的差别。当声明的顺序被交换时,我应该移动where T : M1,因为IHandle&lt;MReset&gt; 约束最终被应用于 T 而不是类声明:

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

【讨论】:

    【解决方案2】:

    @Siram 指出唯一性问题(但不是顺序方面)已在Why does this result in CS0695? 中得到解答:

    C# 语言规范 (https://www.microsoft.com/en-us/download/confirmation.aspx?id=7029) 在 13.4.2 中讨论了“实现接口的唯一性”:“由泛型类型声明实现的接口必须对所有可能的构造类型保持唯一性。”稍后,在描述检查的细节时:“在确定所有可能的构造类型时,不考虑约束声明。”

    为什么会这样,我不确定;也许可以构造嵌套或链式约束,使编译器无法证明唯一性,或者并非所有约束都可以通过程序集进行通信(我认为这对于通用语言规则是必要的)。

    【讨论】:

    • 感谢您强调“在确定所有可能的构造类型时不考虑约束声明。” - 这解释了它的一部分。虽然我仍然对订单方面感到最困惑(我可以看到你和 Siram 都知道)。
    • 显然编译器确实以某种意想不到的方式考虑了约束声明。如果我删除where T : M1,则声明的顺序变得无关紧要,并且两个序列都会导致 CS0695 “...因为它们可能会针对某些类型参数替换统一” - 正如我所期望的那样。
    猜你喜欢
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多