问题描述
我读过 Eric 的文章 此处 关于 foreach 枚举以及 foreach 可以工作的不同场景
为了防止旧的 C# 版本进行装箱,C# 团队为 foreach 启用了鸭子类型以在非可枚举集合上运行.(公共 GetEnumerator
返回具有公共 MoveNext
和 Current
属性就足够了(.
所以,埃里克写了一个示例:
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 type
System.Collections.IEnumerable
接口或实现收集模式
满足所有以下条件:
C 包含一个带有签名 GetEnumerator() 的公共实例方法,该方法返回结构类型、类类型或接口类型,在下文中称为 E.
E 包含一个具有签名 MoveNext() 和返回类型 bool 的公共实例方法.
E 包含一个名为 Current 的公共实例属性,它允许读取当前值.这个属性的类型被称为集合类型的元素类型.
好的.让我们将文档与 Eric 的示例进行匹配
Eric 的示例是 被称为集合类型
,因为它确实实现了System.Collections.IEnumerable
接口(尽管是明确的).但它not(!)是一个collection pattern
,因为bullet 3:MyEnumerator
not公共实例属性命名当前.
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 pattern
和 System.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 returningbool
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` 行为——澄清?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!