C# 为什么可以';t返回列表的方法<;T>;实现一个返回IEnumerable的方法<;T>;?
我有一个具体的类实现了一个带有方法签名的接口C# 为什么可以';t返回列表的方法<;T>;实现一个返回IEnumerable的方法<;T>;?,c#,.net,interface,C#,.net,Interface,我有一个具体的类实现了一个带有方法签名的接口 IEnumerable<T> Foo() IEnumerable Foo() 与 List Foo() 编译器说List Foo无法实现IEnumerable Foo。为什么?因为您无法更改在C#中实现的方法的签名中的返回类型或任何其他类型 C#的语言规范是这样说的。他们为什么做出这个决定还不得而知,但作为一个大胆的猜测,他们可能认为潜在的收益不值得付出代价 但是,您可以做的是将签名保留为IEnumerable,只需返回一个列表为什
IEnumerable<T> Foo()
IEnumerable Foo()
与
List Foo()
编译器说
List Foo
无法实现IEnumerable Foo
。为什么?因为您无法更改在C#中实现的方法的签名中的返回类型或任何其他类型
C#的语言规范是这样说的。他们为什么做出这个决定还不得而知,但作为一个大胆的猜测,他们可能认为潜在的收益不值得付出代价
但是,您可以做的是将签名保留为IEnumerable
,只需返回一个列表
为什么编译器会拒绝这个?
因为规范不允许这样做:
出于接口映射的目的,在以下情况下,类成员a与接口成员B匹配:
- A和B是方法,A和B的名称、类型和形式参数列表是相同的
- [……]
- 因为这两种类型都是引用类型,所以它们保留了表示。因此,这些签名之间存在二进制兼容性
- 返回类型的协方差是类型安全的,因为返回
的方法返回接口调用者期望的List
IEnumerable
- 委托返回类型的协方差:
是一个Func
Func
- 接口协方差:
是一个IEnumerable
IEnumerable我无法回答为什么的问题,但值得注意的是,这个限制几乎没有实际影响,因为显式接口实现允许您使用少量的粘合代码获得所需的效果:
public interface IFooable<T> { IEnumerable<T> Foo(); } public sealed class Fooable<T> : IFooable<T> { public List<T> Foo() { //... } IEnumerable<T> IFooable<T>.Foo() { return Foo(); } }
我们可以看一个例子吗?什么是具体类?它是由岩石制成的吗?我看不出有什么技术上的原因,因为转换是保留表示和类型安全的。事实上,非常相似的转换发生在接口协方差或委托协方差上。但C#规范禁止隐式接口实现,除非存在精确的签名匹配。做出这一决定的原因尚不清楚,但我们可以默认为“有更高优先级的功能,不值得花时间”。这个问题似乎离题了,因为它是关于为什么C#开发人员选择不花时间投资于特定功能。为什么?因为它不是那样设计的。问为什么在某些产品中做出某些设计决策的问题在这里通常是无效的。关于返回类型协方差的相关问题:这完全是错误的。返回类型协方差决不会破坏静态类型。例如,这是Java的一个特性。这只是C#选择不花时间/金钱添加的一项功能。没有真正的技术障碍。你不能在C。这是一个简单的事实,即使是89K积分也必须接受。如果C#的实现者做出了不同的决定,你会怎么做?当然但这不是问题,是吗?“Servy,我认为返回式协方差和隐式接口实现的协方差是不同的,尽管是密切相关的特征。这正是问题所在。就在这个问题上,他明确表示他知道这不会被编译。他知道这不是语言的一部分。他想知道为什么它不是语言的一部分。@CodesInChaos你是对的,尽管现在编辑我的评论已经太晚了。如果这个方法是抽象的而不是接口方法,除了声明一个抽象的“虚拟”之外,还有什么实际的解决方法吗从基类继承的类将抽象方法链接到具有不同名称的方法,从而允许从“dummy”类派生的类实现抽象方法并定义具有不同返回类型的新方法?C#或CIL规范会带来任何困难吗?@supercat 1)更改规范以添加返回类型协方差:这看起来比为接口实现添加协方差更棘手。我认为C#规范的后果不会太严重,但是调整CLR以使用不同的匹配规则进行覆盖可能会有问题。2) 解决方法:我知道没有比拥有一个受保护的虚拟方法和一个公共阴影方法更好的技术了,就像你建议的那样。就我个人而言,我通常避免使用公共虚拟方法,在公共非虚拟方法中进行参数验证,并在受保护的方法中实现可重写功能。有没有办法避免额外的继承层?我当然可以理解,如果基类契约允许父类添加行为,但从概念上讲,如果子派生类需要添加功能,则使用公共非虚拟方法链接到虚拟或抽象方法通常会有好处,这应该通过让第一个派生类实现基类虚拟方法公共接口IFooable { IEnumerable Foo(); } 公共密封类可食:可食 { 公开名单Foo() { //... } IEnumerable IFooable.Foo() { 返回Foo(); } }
来处理,并为要重写的子类声明自己的虚拟方法。在每个阶段都必须使用新名称似乎有点棘手。我认为问题的很大一部分来自这样一个事实,即.NET假定类公开给公众的面应该是它公开给派生类对象的面的子集,但有时确实不应该是这样。具体类可以有构造函数或虚拟方法imfinal
class FooClass<T> : IFoo { public List<T> Foo() { //do something } IEnumerable<T> IFoo.Foo() { return Foo(); } }
public interface IFooable<T> { IEnumerable<T> Foo(); } public sealed class Fooable<T> : IFooable<T> { public List<T> Foo() { //... } IEnumerable<T> IFooable<T>.Foo() { return Foo(); } }