【问题标题】:The return type of the members on an Interface Implementation must match exactly the interface definition?接口实现上成员的返回类型必须与接口定义完全匹配?
【发布时间】:2011-11-03 13:59:36
【问题描述】:

根据 CSharp 语言规范。

接口定义了一个可以由类实现的契约,并且 结构。接口不提供成员的实现 它定义了——它仅仅指定了必须由 实现接口的类或结构。

所以我有这个:

interface ITest
{
    IEnumerable<int> Integers { get; set; }
}

我的意思是。 “我与一个属性签订了一个合同,该属性是您可以枚举的整数集合”。

那我想要下面的接口实现:

class Test : ITest
{
    public List<int> Integers { get; set; }
}

我得到以下编译器错误:

'Test' 没有实现接口成员'ITest.Integers'。 'Test.Integers' 不能实现 'ITest.Integers' 因为它没有 具有匹配的返回类型 'System.Collections.Generic.IEnumerable'。

只要我可以说我的 Test 类实现了 ITest 契约,因为 int 属性的 List 实际上是 int 的 IEnumerable。

那么 c# 编译器告诉我错误的方式是什么?

【问题讨论】:

  • 签名不一样。如果要实现接口,则需要同样实现(协变/逆变泛型类型参数除外)。
  • 同上詹姆斯所说的IEnumerable&lt;int&gt; != List&lt;int&gt;
  • 如果您只想要“get”,理论上应该是可能的。请参阅下面 Eric Lippert 的帖子,了解如何在 c# 中实现这一点。在某些情况下,也可以只创建两个方法,一个称为 IntegerList,一个称为 IntegerEnumerable..,然后 IntegerEnumerable 实现在内部调用另一个方法。

标签: c#


【解决方案1】:

仅供参考,您想要的功能称为“虚拟方法返回类型协方差”,正如您所发现的,C# 不支持它。这是其他面向对象语言的特性,例如 C++。

虽然我们经常收到对此功能的请求,但我们没有计划将其添加到语言中。这不是一个可怕的功能。如果我们有它,我会使用它。但是我们有很多理由不这样做,包括它不受 CLR 支持,它为可版本控制的组件添加了新的有趣的故障模式,Anders 认为它​​不是一个非常有趣的特性,而且我们有很多很多更高的优先级和有限的预算。

顺便说一句,尽管人们一直要求我们提供虚方法返回类型协变,但没有人要求虚方法形式参数类型逆变,尽管从逻辑上讲它们本质上是相同的特性。也就是说,我有一个采用 Giraffe 的虚拟方法/接口方法 M,我想用采用 Animal 的方法 M 覆盖它/实现它。

【讨论】:

  • @RomanR。我猜他的意思是Anders Hejlsberg
  • virtual method formal parameter type contravariance 字太多了。人们只要求他们可以轻松描述的东西:)
  • @Jason:在给定的示例中,您是正确的; get/set 属性不能安全地在其类型中协变。但是,如果只有一个 getter,并且它的返回类型已知是一个引用类型,那么为了实现的目的,它可以安全地成为协变的。 (同样,如果它是引用类型的仅集合属性,那么它可以安全地设为逆变。)如果我们支持该功能,我们不支持。
  • @Eric Lippert:谢谢,很有道理。所以实际上这里没有他“想要”的功能,在这种情况下他的特定问题无法解决。
  • @fuglede:我已经四年多没有在C#团队工作了;在 GitHub 上请求他们!
【解决方案2】:

你不能这样做,因为如果允许的话,你手头上会有一个大问题,具体取决于实现。考虑:

interface ITest
{
    IEnumerable<int> Integers { get; set; }
}

class Test : ITest
{
    // if this were allowed....
    public List<int> Integers { get; set; }
}

这将允许:

ITest test = new Test();
test.Integers = new HashSet<int>();

这将使 Test 的合同无效,因为 Test 说它包含 List&lt;int&gt;

现在,您可以使用显式接口实现来允许它满足两个签名,具体取决于它是从 ITest 引用还是 Test 引用中调用的:

class Test : ITest
{
    // satisfies interface explicitly when called from ITest reference
    IEnumerable<int> ITest.Integers
    {
        get
        {
            return this.Integers; 
        }
        set
        {
            this.Integers = new List<int>(value);
        }
    }

    // allows you to go directly to List<int> when used from reference of type Test
    public List<int> Integers { get; set; }
}

【讨论】:

    【解决方案3】:

    简单的事实是,如果一个界面说:

    IInterface{
       Animal A { get; }
    }
    

    那么该属性的实现必须与类型完全匹配。尝试将其实现为

    MyClass : IInterface{
      Duck A { get; }
    }
    

    不起作用 - 即使 DuckAnimal

    您可以这样做:

    MyClass : IInterface{
      Duck A { get; }
      Animal IInterface.A { get { return A; } }
    }
    

    即提供IInterface.A 成员的显式实现,利用DuckAnimal 之间的类型关系。

    在您的情况下,这意味着至少将 getter 实现为 ITest.Integers

    IEnumerable<int> ITest.Integers { get { return Integers; } }
    

    要实现 setter,您需要乐观地进行转换或对输入值使用 .ToList()。

    请注意,在这些显式实现中使用AIntegers 不是递归的,因为显式接口实现在类型的公共视图中是隐藏的——它们只有在调用者通过它与类型对话时才会生效IInterface/ITest接口实现。

    【讨论】:

      【解决方案4】:

      您需要规范中的 13.4.4:

      出于接口映射的目的,类成员 A 与接口成员 B 匹配时:

      AB是属性,A和B的名称和类型相同,A和B具有相同的访问器(如果A 不是显式接口成员实现,则允许其拥有额外的访问器。

      此外,您认为List&lt;int&gt; Integers { get; set; } 满足IEnumerable&lt;int&gt; Integers { get; set; } 的合同是错误的。即使规范以某种方式放宽以不要求返回类型相同,请注意带有公共设置器的List&lt;int&gt; 类型的属性与带有公共设置器的IEnumerable&lt;int&gt; 类型的属性在任何地方都不相同,因为后者可以分配int[] 的实例,但不能分配给前者。

      【讨论】:

        【解决方案5】:

        你可以这样做:

         interface ITest
        {
            IEnumerable<int> Integers { get; set; }
        }
        
        class Test : ITest
        {
            public IEnumerable<int> Integers { get; set; }
        
            public Test()
            {
                Integers = new List<int>();
            }
        }
        

        【讨论】:

          【解决方案6】:

          因为 Test 不是 ITest。为什么 ?使用 ITest,您可以将数组设置为属性 Integers。但是你不能通过测试。 使用 .net 4.0,您可以执行类似的操作(协变和逆变),但并非完全如此,每种语言都不正确。

          【讨论】:

          • 您将协方差误认为是泛型类型可分配性,而协方差是虚拟返回类型;虽然相似,但它们是不同类型的协方差。 C# 4 支持泛型类型协变,但不支持返回类型协变。
          • 不不,我没有混淆。我向他解释了为什么在他的情况下它不是协变而不是逆变,这是不正确的。 (请埃里克永远不要教我协变和逆变,这些术语来自法国数学家,我们知道如何使用它们以及它们的真正含义)
          • 原帖者希望使用虚方法返回类型协方差。这与 泛型类型分配兼容性协方差 不同。我要指出的是,说 .NET 4.0 允许您“做那样的事情”是令人困惑的,因为不清楚“那样的事情”的先行词是否是虚拟方法返回类型协方差泛型类型赋值兼容性协方差
          • 但是如果将返回类型指定为泛型的参数类型呢?就像在 MyType ?
          • 此外,它并非在每种语言中都不正确。有些语言允许非类型安全的协变和逆变操作。 C# 和 Java 允许不安全的数组协方差。 Eiffel 支持虚方法形式参数类型协方差,奇怪的是,Eiffel 有一个不同寻常的类型系统。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-08-15
          • 1970-01-01
          • 1970-01-01
          • 2022-09-23
          相关资源
          最近更新 更多