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

Posted

技术标签:

【中文标题】为啥我不能使用数组的枚举器,而不是自己实现呢?【英文标题】:Why can't I use the enumerator of an array, instead of implementing it myself?为什么我不能使用数组的枚举器,而不是自己实现呢? 【发布时间】:2011-02-24 21:46:18 【问题描述】:

我有一些这样的代码:

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 ( );
    

但是编译器抱怨说:

"不能隐式转换类型 'System.Collections.IEnumerator' 到 'System.Collections.Generic.IEnumerator'。 存在显式转换(您是 缺少演员表?)”

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

【问题讨论】:

见***.com/questions/4482557/… 【参考方案1】:

这是一个微妙的,有点不幸。简单的解决方法是:

public IEnumerator<object> GetEnumerator ( )

     return ((IEnumerable<object>)this.Values).GetEnumerator ( );     
 

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

规则是:

System.Array 使用公共方法“隐式”实现 IEnumerable。 每个数组类型 T[] 都继承自 System.Array。 每个数组类型 T[] 都实现了IList&lt;T&gt;IEnumerable&lt;T&gt; 等等。 因此每个数组类型 T[] 都可以转换为IEnumerable&lt;T&gt;

注意第三点不是

每个数组类型 T[] 实现 IList&lt;T&gt;IEnumerable&lt;T&gt;在 T[] 上定义的公共方法和属性隐式实现成员

然后就可以了。当您查找 GetEnumerator 时,我们在 object[] 上查找它并没有找到它,因为 object[] 实现了IEnumerable&lt;object&gt;显式。它可转换IEnumerable&lt;object&gt;,并且可转换性不计入查找。 (你不会仅仅因为 int 可以转换为 double 而期望“double”的方法出现在 int 上。)然后我们查看基本类型,发现 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  ...  
    ...

当您在 object[] 上调用 GetEnumerator 时,我们看不到作为显式接口实现的实现,因此我们转到基类,它确实有一个可见的。

所有 object[]、int[]、string[]、SomeType[] 类是如何“动态”生成的?

魔法!

这不是泛型,对吧?

没错。数组是非常特殊的类型,它们深入到 CLR 类型系统中。尽管它们在很多方面都与泛型非常相似。

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

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

您认为哪个更好:将GetEnumerator() 转换为IEnumerable&lt;object&gt;,还是只使用foreachyield

问题格式不正确。您不会将 GetEnumerator 转换为 IEnumerable&lt;object&gt;。您可以将 array 转换为 IEnumerable&lt;object&gt;,或者将 GetEnumerator 转换为 IEnumerator&lt;object&gt;

我可能会将 Values 转换为 IEnumerable&lt;object&gt; 并在其上调用 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

第一个声明静默实现了方法。第二个是明确关于它实现了什么接口方法。

像这样实现IEnumerable&lt;T&gt;,而不是用公共方法隐式实现的原因是什么?我很好奇,因为你说“这是一个微妙的,有点不幸”,所以这似乎是因为我想是因为一个较早的决定迫使你这样做?

我不知道是谁做出了这个决定。不过,这有点不幸。至少有一个用户——你——弄糊涂了,我也弄糊涂了几分钟!

我原以为 Array 类型会是这样的:public class Array&lt;T&gt; : IEnumerable&lt;T&gt; 等等。但实际上有一些关于它的神奇代码,对吧?

没错。正如您昨天在问题中指出的那样,如果我们在 CLR v1 中使用泛型,情况会大不相同。

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

下次您设计类型系统时,将泛型放入 v1 中,并确保您从一开始就将强集合类型、可空类型和不可空类型嵌入到框架中。事后添加泛型和可为空的值类型很困难。

【讨论】:

由 Eric 编译,未查看规范。 @Metro:如果你查看编辑历史,你会发现,当我查看规范时,我首先认为它是错误的,然后我意识到我错了,规范是正确的.故事的寓意:先看规范 @Eric - 实际上,该评论是对您无需查看规格即可提供准确答案的能力的颂歌。这次您实际上必须查看规范只是一个巧合;)在查看我的评论以及您实际所做的事情时,我可以看到我的评论可能被认为是粗鲁的,这是与意图相差 180 度。 @Metro:的确,讽刺的是,这是我把事情搞砸的一个例子。两次! (而且我没有想到粗鲁的解释。) @Joan:我认为可能会有一些特殊的数组类型,因为你想保证数组元素是内存中连续的变量,这意味着 GC 对它们有特殊的了解。但对用户来说,我想它们似乎只是泛型类型,就像 int 一样?只是泛型类型 Nullable,所以 int[] 只是 Array.【参考方案2】:

您必须将数组转换为 IEnumerable&lt;object&gt; 才能访问通用枚举器:

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

【讨论】:

以上是关于为啥我不能使用数组的枚举器,而不是自己实现呢?的主要内容,如果未能解决你的问题,请参考以下文章

二维数组名不能赋值给二级指针- -

为啥 BCL 集合使用结构枚举器,而不是类?

为啥我们在枚举中写 Integer 而不是 int? [复制]

求教我的tomcat配置了域名之后,为啥域名访问正常,而ip反而不能访问呢

在片段着色器中,为啥我不能使用平面输入整数来索引 sampler2D 的统一数组?

为啥我们不能在跳转搜索中使用二分搜索而不是线性搜索?