为什么我不能使用数组的枚举器,而不是自己实现它?

Joa*_*nge 22 .net c# linq arrays ienumerable

我有一些像这样的代码:

public class EffectValues : IEnumerable<object>
{
    public object [ ] Values { get; set; }

    public IEnumerator<object> GetEnumerator ( )
    {
        return this.Values.GetEnumerator ( );
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ( )
    {
        return this.GetEnumerator ( );
    }
}
Run Code Online (Sandbox Code Playgroud)

但编译器抱怨说:

"无法将类型'System.Collections.IEnumerator'隐式转换为'System.Collections.Generic.IEnumerator'.存在显式转换(您是否错过了转换?)"

我以为Array类型实现了IEnumerable接口,不是吗?因为我可以直接在Values实例上使用Linq功能.

Eri*_*ert 41

这是一个微妙而有点不幸的事.简单的解决方法是:

public IEnumerator<object> GetEnumerator ( )
{
     return ((IEnumerable<object>)this.Values).GetEnumerator ( );     
} 
Run Code Online (Sandbox Code Playgroud)

我以为Array类型实现了IEnumerable接口,不是吗?

规则是:

  • System.Array使用公共方法"隐式"实现IEnumerable.
  • 每个数组类型T []都继承自System.Array.
  • 每个数组类型T []实现IList<T>,IEnumerable<T>等等.
  • 因此每个数组类型T []都可以转换为 IEnumerable<T>

请注意,第三点不是

  • 每个数组类型T []实现IList<T>,IEnumerable<T>依此类推,在T []上定义的公共方法和属性隐式实现成员

你去吧 当你查找GetEnumerator时,我们在object []上查找并找不到它,因为object [] IEnumerable<object> 显式实现.它可以转换IEnumerable<object>,并且可转换性不计入查找.(你不会期望在int上出现"double"方法只是因为int可以转换为double.)然后我们查看基类型,发现System.Array使用公共方法实现IEnumerable,所以我们已经找到了我们的GetEnumerator.

也就是说,想想这样:

namespace System
{
    abstract class Array : IEnumerable
    {
        public IEnumerator GetEnumerator() { ... }
        ...
    }
}

class object[] : System.Array, IList<object>, IEnumerable<object>
{
    IEnumerator<object> IEnumerable<object>.GetEnumerator() { ... }
    int IList<object>.Count { get { ... } }
    ...
}
Run Code Online (Sandbox Code Playgroud)

当你在object []上调用GetEnumerator时,我们看不到实现是一个显式的接口实现,所以我们去基类,它有一个可见的.

如何"动态"生成所有object [],int [],string [],SomeType []类?

魔法!

这不是泛型,对吧?

对.数组是非常特殊的类型,它们在深入的CLR类型系统中被烘焙.虽然它们在许多方面与泛型非常相似.

看起来这class object [] : System.Array是用户无法实现的,对吧?

对,这只是为了说明如何思考它.

你觉得哪一个更好:GetEnumerator()投入IEnumerable<object>,或只是使用foreachyield

问题是形象不对称.您没有将GetEnumerator强制转换IEnumerable<object>.您可以将数组转换IEnumerable<object>或者将GetEnumerator转换IEnumerator<object>.

我可能会将值转换为IEnumerable<object>并调用GetEnumerator它.

我可能会使用强制转换,但我想知道这是否是你或一些程序员可以阅读代码的地方,会认为它不太清楚.

我认为演员阵容非常清楚.

当你说隐式实现时,你的意思是以Interface.Method的形式,对吧?

不,相反:

interface IFoo { void One(); void Two(); }
class C : IFoo
{
    public void One() {} // implicitly implements IFoo.One
    void IFoo.Two() {} // explicitly implements IFoo.Two
}
Run Code Online (Sandbox Code Playgroud)

第一个声明默默地实现该方法.第二个是明确它实现的接口方法.

这样实现的原因是什么IEnumerable<T>,而不是公共方法的隐式实现?我很好奇因为你说"这是一个微妙而有点不幸",所以看起来这是因为一个较老的决定迫使你这样做我想象?

我不知道是谁做出了这个决定.不过有点不幸.它至少让一个用户感到困惑 - 你 - 它也让我困惑了几分钟!

我原以为Array类型会是这样的:public class Array<T> : IEnumerable<T>等等.但是有一些神奇的代码呢,对吧?

对.正如你在昨天的问题中所指出的那样,如果我们在CLR v1中使用泛型,事情会有很大的不同.

数组本质上是一种通用的集合类型.因为它们是在没有泛型的类型系统中创建的,所以类型系统中必须有许多特殊代码来处理它们.

下次设计类型系统时,将泛型置于v1中,并确保获得强大的集合类型,从一开始就将可空类型和非可空类型烘焙到框架中.事后添加泛型和可空值类型很困难.

  • @Metro:如果你看一下编辑历史,你会看到当我看到规范时,我首先想到它是错的,然后我意识到我错了,规范是正确的.故事的寓意:*先看看规格*. (2认同)
  • @Eric--实际上,评论是对你提供准确答案的能力的颂歌,甚至没有看到规格.只是巧合,你实际上*曾经*看过这个规格;)在查看我的评论以及你实际做了什么时,我可以看到我的评论可能已经脱离了粗鲁,这与意图相差180度. (2认同)
  • @Metro:的确,这是一个多么具有讽刺意味的例子,当时我把它搞砸了.两次!(我没有想到粗鲁的解释.) (2认同)