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

发布于 2024-10-19 06:12:58 字数 638 浏览 2 评论 0原文

我有一些这样的代码:

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 功能。

I have some code like this:

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

But the compiler complains saying:

"Cannot implicitly convert type
'System.Collections.IEnumerator' to
'System.Collections.Generic.IEnumerator'.
An explicit conversion exists (are you
missing a cast?)"

I thought the Array type implemented both IEnumerable interfaces, does it not? Because I can use Linq features on the Values instance directly.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

沫尐诺 2024-10-26 06:12:58

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

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

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

规则是:

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

请注意,第三点不是

  • 每个数组类型 T[] 都实现 IListIEnumerable在 T[] 上定义隐式实现成员的公共方法和属性

就这样。当您查找 GetEnumerator 时,我们会在 object[] 上查找它,但没有找到它,因为 object[] 显式实现了 IEnumerable。它可转换IEnumerable,并且可转换性不会计入查找。 (你不会期望“double”的方法出现在 int 上,只是因为 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 { ... } }
    ...
}

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

如何“即时”生成所有 object[]、int[]、string[]、SomeType[] 类?

魔法!

这不是泛型,对吧?

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

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

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

您认为哪一个更好:将 <​​code>GetEnumerator() 转换为 IEnumerable,或者仅使用 foreach产量

这个问题的格式不正确。您无需将 GetEnumerator 强制转换为 IEnumerable。您可以将数组强制转换为IEnumerable,或者将GetEnumerator强制转换为IEnumerator

我可能会将 Values 转换为 IEnumerable并对其调用 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,而不是使用公共方法隐式实现?我很好奇,因为你说“这是一个微妙且有点不幸的”,所以我想这似乎是因为一个较早的决定迫使你这样做?

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

我本以为数组类型会是这样的:public class Array: IEnumerable 等。但是,有一些关于它的神奇代码,对吗?

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

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

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

This is a subtle and a bit unfortunate. The easy workaround is:

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

I thought the Array type implemented both IEnumerable interfaces, does it not?

The rules are:

  • System.Array implements IEnumerable "implicitly", with public methods.
  • every array type T[] inherits from System.Array.
  • every array type T[] implements IList<T>, IEnumerable<T> and so on.
  • therefore every array type T[] is convertible to IEnumerable<T>

Notice that the third point was NOT

  • every array type T[] implements IList<T>, IEnumerable<T> and so on with public methods and properties defined on T[] that implicitly implement the members

And there you go. When you look up GetEnumerator, we look it up on object[] and don't find it, because object[] implements IEnumerable<object> explicitly. It is convertible to IEnumerable<object>, and convertibility doesn't count for lookups. (You wouldn't expect a method of "double" to appear on int just because int is convertible to double.) We then look at the base type, and find that System.Array implements IEnumerable with a public method, so we've found our GetEnumerator.

That is, think about it like this:

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 { ... } }
    ...
}

When you call GetEnumerator on object[], we don't see the implementation that is an explicit interface implementation, so we go to the base class, which does have one visible.

How do all the object[], int[], string[], SomeType[] classes get generated "on the fly"?

Magic!

This is not generics, right?

Right. Arrays are very special types and they are baked in at a deep level into the CLR type system. Though they are very similar to generics in a lot of ways.

It seems like this class object [] : System.Array is something that can't be implemented by a user, right?

Right, that was just to illustrate how to think about it.

Which one do you think is better: Casting the GetEnumerator() to IEnumerable<object>, or just use foreach and yield?

The question is ill-formed. You don't cast the GetEnumerator to IEnumerable<object>. You either cast the array to IEnumerable<object> or you cast the GetEnumerator to IEnumerator<object>.

I would probably cast Values to IEnumerable<object> and call GetEnumerator on it.

I will probably use casting but I am wondering if this is a place where you or some programmer who could read the code, would think it's less clear.

I think it's pretty clear with the cast.

when you said implicit implementation, you mean in the form of Interface.Method, right?

No, the opposite:

interface IFoo { void One(); void Two(); }
class C : IFoo
{
    public void One() {} // implicitly implements IFoo.One
    void IFoo.Two() {} // explicitly implements IFoo.Two
}

The first declaration silently implements the method. The second is explicit about what interface method it implements.

What's the reason for implementing IEnumerable<T> like that, instead of implicit implementation with public methods? I got curious because you said "This is a subtle and a bit unfortunate", so it seems like it's because of an older decision that forced you to do this I imagine?

I don't know who made this decision. It is kind of unfortunate though. It's confused at least one user -- you -- and it confused me for a few minutes there too!

I would have thought the Array type would be something like this: public class Array<T> : IEnumerable<T> etc. But instead there is some magical code about it then, right?

Right. As you noted in your question yesterday, things would have been a lot different if we'd had generics in CLR v1.

Arrays are essentially a generic collection type. Because they were created in a type system that did not have generics, there has to be lots of special code in the type system to handle them.

Next time you design a type system put generics in v1 and make sure you get strong collection types, nullable types and non-nullable types baked in to the framework from the beginning. Adding generics and nullable value types post hoc was difficult.

倾城月光淡如水﹏ 2024-10-26 06:12:58

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

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

You have to cast the array to IEnumerable<object> to be able to access the generic enumerator:

public IEnumerator<object> GetEnumerator() {
  return ((IEnumerable<object>)this.Values).GetEnumerator();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文