本文介绍了C#`foreach` 行为——澄清?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我读过 Eric 的文章 此处 关于 foreach 枚举以及 foreach 可以工作的不同场景

为了防止旧的 C# 版本进行装箱,C# 团队为 foreach 启用了鸭子类型以在非可枚举集合上运行.(公共 GetEnumerator 返回具有公共 MoveNextCurrent 属性就足够了(.

所以,埃里克写了一个示例:

class MyIntegers : IEnumerable{公共类 MyEnumerator : IEnumerator{私有整数索引 = 0;对象 IEnumerator.Current { 返回 this.Current;}int Current { 返回索引 * 索引;}public bool MoveNext(){如果(索引 > 10)返回假;++索引;返回真;}}public MyEnumerator GetEnumerator() { return new MyEnumerator();}IEnumerator IEnumerable.GetEnumerator() { 返回 this.GetEnumerator();}}

但我相信它有一些拼写错误(在 Current 属性实现中缺少 get 访问器),这会阻止它编译(我已经给他发了电子邮件).

无论如何这里是一个工作版本:

class MyIntegers : IEnumerable{公共类 MyEnumerator : IEnumerator{私有整数索引 = 0;公共无效重置(){抛出新的 NotImplementedException();}对象 IEnumerator.Current {得到 { 返回 this.Current;}}整数电流{得到{返回索引*索引;}}public bool MoveNext(){如果(索引 > 10)返回假;++索引;返回真;}}public MyEnumerator GetEnumerator() { return new MyEnumerator();}IEnumerator IEnumerable.GetEnumerator() { 返回 this.GetEnumerator();}}

好的.

根据 MSDN:

如果一个类型C实现了collection typeSystem.Collections.IEnumerable 接口实现收集模式满足所有以下条件:

  • C 包含一个带有签名 GetEnumerator() 的公共实例方法,该方法返回结构类型、类类型或接口类型,在下文中称为 E.

  • E 包含一个具有签名 MoveNext() 和返回类型 bool 的公共实例方法.

  • E 包含一个名为 Current 的公共实例属性,它允许读取当前值.这个属性的类型被称为集合类型的元素类型.

好的.让我们将文档与 Eric 的示例进行匹配

Eric 的示例 被称为集合类型,因为它确实实现了System.Collections.IEnumerable 接口(尽管是明确的).但它not(!)是一个collection pattern,因为bullet 3:MyEnumeratornot公共实例属性命名当前.

MSDN 说:>

如果集合表达式的类型实现了集合模式(如上定义),foreach 的扩展声明是:

E enumerator = (collection).GetEnumerator();尝试 {while (enumerator.MoveNext()) {ElementType element = (ElementType)enumerator.Current;陈述;}}最后 {IDisposable Disposable = enumerator as System.IDisposable;if (disposable != null)disposable.Dispose();}

否则,集合表达式是一种实现System.IEnumerable(!),而foreach语句的展开是:

IEnumerator 枚举数 =((System.Collections.IEnumerable)(collection)).GetEnumerator();尝试 {while (enumerator.MoveNext()) {ElementType element = (ElementType)enumerator.Current;陈述;}}最后 {IDisposable Disposable = enumerator as System.IDisposable;if (disposable != null)disposable.Dispose();}

问题 #1

似乎 Eric 的示例既没有实现collection patternSystem.IEnumerable - 所以它不应该匹配上面指定的任何条件.那么为什么我仍然可以通过以下方式对其进行迭代:

 foreach (var element in (new MyIntegers() as IEnumerable )){Console.WriteLine(元素);}

问题 #2

为什么我必须提到 new MyIntegers() 作为 IEnumerable ?它已经是可枚举的 (!!) 甚至在那之后,编译器是不是已经通过强制转换自己完成了这项工作:

((System.Collections.IEnumerable)(collection)).GetEnumerator() ?

就在这里:

 IEnumerator 枚举数 =((System.Collections.IEnumerable)(collection)).GetEnumerator();尝试 {while (enumerator.MoveNext()) {...

那为什么它仍然要我提到 Ienumerable ?

解决方案

是的 - 或者更确切地说,如果 Current 是公开的.所需要的只是它有:

  • 一个公开的、可读的Current属性
  • 一个没有类型参数的公共 MoveNext() 方法返回 bool

这里缺少 public 只是另一个错误,基本上.事实上,这个例子没有做它的意思(防止拳击).它使用 IEnumerable 实现,因为您使用 new MyIntegers() 作为 IEnumerable - 所以表达式类型是 IEnumerable,它只使用整个界面.

您声称它没有实现 IEnumerable,(即 System.Collections.IEnumerable,顺便说一句)但它确实使用了显式接口实现.

测试这类事情最容易根本不实现IEnumerable:

使用系统;类 BizarreCollection{公共枚举器 GetEnumerator(){返回新的枚举器();}公共类枚举器{私有整数索引 = 0;public bool MoveNext(){如果(索引== 10){返回假;}指数++;返回真;}公共 int 当前 { 获取 { 返回索引;} }}}课堂测试{静态无效主(字符串 [] args){foreach(新 BizarreCollection() 中的 var 项目){Console.WriteLine(item);}}}

现在,如果您将 Current 设为私有,它将无法编译.

I've read Eric's article here about foreach enumeration and about the different scenarios where foreach can work

In order to prevent the old C# version to do boxing , the C# team enabled duck typing for foreach to run on a non- Ienumerable collection.(A public GetEnumerator that return something that has public MoveNext and Current property is sufficient(.

So , Eric wrote a sample :

class MyIntegers : IEnumerable
{
  public class MyEnumerator : IEnumerator
  {
    private int index = 0;
    object IEnumerator.Current { return this.Current; }
    int Current { return index * index; }
    public bool MoveNext()
    {
      if (index > 10) return false;
      ++index;
      return true;
    }
  }
  public MyEnumerator GetEnumerator() { return new MyEnumerator(); }
  IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}

But I believe it has some typos (missing get accessor at Current property implementation) which prevent it from compiling (I've already Emailed him).

Anyway here is a working version :

class MyIntegers : IEnumerable
{
  public class MyEnumerator : IEnumerator
  {
    private int index = 0;
      public void Reset()
      {
          throw new NotImplementedException();
      }

      object IEnumerator.Current {
          get { return this.Current; }
      }
    int Current {
        get { return index*index; }
    }
    public bool MoveNext()
    {
      if (index > 10) return false;
      ++index;
      return true;
    }
  }
  public MyEnumerator GetEnumerator() { return new MyEnumerator(); }
  IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}

Ok.

According to MSDN :

  • C contains a public instance method with the signature GetEnumerator() that returns a struct-type, class-type, or interface-type, which is called E in the following text.

  • E contains a public instance method with the signature MoveNext() and the return type bool.

  • E contains a public instance property named Current that permits reading the current value. The type of this property is said to be the element type of the collection type.

OK. Let's match the docs to Eric's sample

Eric's sample is said to be a collection type because it does implements the System.Collections.IEnumerable interface ( explicitly though). But it is not(!) a collection pattern because of bullet 3 : MyEnumerator does not public instance property named Current.

MSDN says :

E enumerator = (collection).GetEnumerator();
try {
   while (enumerator.MoveNext()) {
      ElementType element = (ElementType)enumerator.Current;
      statement;
   }
}
finally {
   IDisposable disposable = enumerator as System.IDisposable;
   if (disposable != null) disposable.Dispose();
}
IEnumerator enumerator =
        ((System.Collections.IEnumerable)(collection)).GetEnumerator();
try {
   while (enumerator.MoveNext()) {
      ElementType element = (ElementType)enumerator.Current;
      statement;
   }
}
finally {
   IDisposable disposable = enumerator as System.IDisposable;
   if (disposable != null) disposable.Dispose();
}

Question #1

It seems that Eric's sample neither implements the collection pattern nor System.IEnumerable - so it's not supposed to match any of the condition specified above. So how come I can still iterate it via :

 foreach (var element in (new MyIntegers() as IEnumerable ))
             {
                 Console.WriteLine(element);
             }

Question #2

Why do I have to mention new MyIntegers() as IEnumerable ? it's already Ienumerable (!!) and even after that , Isn't the compiler is already doing the job by itself via casting :

((System.Collections.IEnumerable)(collection)).GetEnumerator() ?

It is right here :

 IEnumerator enumerator =
            ((System.Collections.IEnumerable)(collection)).GetEnumerator();
    try {
       while (enumerator.MoveNext()) {
       ...

So why it still wants me to mention as Ienumerable ?

解决方案

Yes it does - or rather, it would if Current were public. All that's required is that it has:

  • A public, readable Current property
  • A public MoveNext() method with no type arguments returning bool

The lack of public here was just another typo, basically. As it is, the example doesn't do what it's meant to (prevent boxing). It's using the IEnumerable implementation because you're using new MyIntegers() as IEnumerable - so the expression type is IEnumerable, and it just uses the interface throughout.

You claim that it doesn't implement IEnumerable, (which is System.Collections.IEnumerable, btw) but it does, using explicit interface implementation.

It's easiest to test this sort of thing without implementing IEnumerable at all:

using System;

class BizarreCollection
{
    public Enumerator GetEnumerator()
    {
        return new Enumerator();
    }

    public class Enumerator
    {
        private int index = 0;

        public bool MoveNext()
        {
            if (index == 10)
            {
                return false;
            }
            index++;
            return true;
        }

        public int Current { get { return index; } }
    }
}

class Test
{
    static void Main(string[] args)
    {
        foreach (var item in new BizarreCollection())
        {
            Console.WriteLine(item);
        }
    }
}

Now if you make Current private, it won't compile.

这篇关于C#`foreach` 行为——澄清?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-23 03:19
查看更多