接口实现上成员的返回类型必须与接口定义完全匹配

本文关键字:接口 定义 实现 成员 返回类型 | 更新日期: 2023-09-27 18:13:08

根据CSharp语言规范。

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

所以我有这个:

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

我的意思是。"我有一个合约,它的属性是一组可以枚举的整数"

然后我想要以下接口实现:

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

我得到以下编译错误:

'Test'没有实现接口成员'Test . integers '。的测试。整数"不能实现"ittest。因为它不是整数是否有匹配的返回类型"System.Collections.Generic.IEnumerable"。

只要我能说我的Test类实现了ITest契约因为List of int属性实际上是int的IEnumerable

那么c#编译器是如何告诉我错误的?

接口实现上成员的返回类型必须与接口定义完全匹配

供参考,你想要的特性被称为"虚拟方法返回类型协方差",正如你所发现的,c#不支持它。它是其他面向对象语言的一个特性,比如c++。

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

顺便提一下,尽管人们一直要求我们虚拟方法返回类型协方差,但没有人要求虚拟方法形式参数类型逆变,即使逻辑上它们本质上是相同的特性。也就是说,我有一个虚拟方法/接口方法M,它需要长颈鹿,我想用一个方法M来覆盖它/实现它,它需要一个动物。

您不能这样做,因为如果允许这样做的话,您将面临一个主要问题,这取决于实现。考虑:

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<int>

现在,可以使用显式接口实现来允许它满足两个签名,这取决于它是从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; }
}

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

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。整数

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

要实现setter,需要对输入值进行乐观类型强制转换或使用. tolist()。

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

您需要规范中的13.4.4:

对于接口映射,类成员A在以下情况下匹配接口成员B:

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

另外,你认为List<int> Integers { get; set; }满足IEnumerable<int> Integers { get; set; }的合同是错误的。即使规范以某种方式放宽,不要求返回类型相同,请注意,具有公共setter的List<int>类型的属性与具有公共setter的IEnumerable<int>类型的属性也不完全相同,因为您可以为后者分配int[]的实例,但对前者则不能。

你可以这样做:

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

因为Test不是一个ITest。为什么?使用ITest,您可以将数组设置为属性Integers。但是你不能通过考试。在。net 4.0中,你可以做这样的事情(协方差和反方差),但不完全是这样,这在每种语言中都是不正确的。