Class是否需要实现IEnumerable才能使用Foreach

这是在C#中,我有一个类,我从其他人的DLL使用。 它没有实现IEnumerable,但有2个传递IEnumerator的方法。 有没有办法可以在这些上使用foreach循环。 我正在使用的课程是密封的。

与流行的看法相反, foreach不需要IEnumerable 。 它需要的是一个方法GetEnumerator ,它返回任何具有方法MoveNext对象和具有相应签名的get-property Current

/编辑:但是,在你的情况下,你运气不好。 但是,您可以简单地包装您的对象,以使其可枚举:

 class EnumerableWrapper { private readonly TheObjectType obj; public EnumerableWrapper(TheObjectType obj) { this.obj = obj; } public IEnumerator GetEnumerator() { return obj.TheMethodReturningTheIEnumerator(); } } // Called like this: foreach (var xyz in new EnumerableWrapper(yourObj)) …; 

/编辑:如果方法返回IEnumerator ,由几个人提出的以下方法不起作用:

 foreach (var yz in yourObj.MethodA()) …; 

Re:如果foreach不需要显式接口契约,它是否使用reflection找到GetEnumerator?

(我不能评论,因为我没有足够高的声誉。)

如果你暗示运行时reflection,那么没有。 它完成所有编译时间,另一个鲜为人知的事实是它还检查可能实现IEnumerator的返回对象是否是一次性的。

要查看此操作,请考虑此(可运行)代码段。

 using System; using System.Collections.Generic; using System.Text; namespace ConsoleApplication3 { class FakeIterator { int _count; public FakeIterator(int count) { _count = count; } public string Current { get { return "Hello World!"; } } public bool MoveNext() { if(_count-- > 0) return true; return false; } } class FakeCollection { public FakeIterator GetEnumerator() { return new FakeIterator(3); } } class Program { static void Main(string[] args) { foreach (string value in new FakeCollection()) Console.WriteLine(value); } } } 

根据MSDN :

 foreach (type identifier in expression) statement 

表达式是:

对象集合或数组表达式。 集合元素的类型必须可转换为标识符类型。 不要使用计算结果为null的表达式。 计算实现IEnumerable的类型或声明GetEnumerator方法的类型。 在后一种情况下,GetEnumerator应返回实现IEnumerator的类型或声明IEnumerator中定义的所有方法。

简短回答:

您需要一个名为GetEnumerator的方法的类,它返回您已有的IEnumerator。 使用简单的包装器实现此目的:

 class ForeachWrapper { private IEnumerator _enumerator; public ForeachWrapper(Func enumerator) { _enumerator = enumerator; } public IEnumerator GetEnumerator() { return _enumerator(); } } 

用法:

 foreach (var element in new ForeachWrapper(x => myClass.MyEnumerator())) { ... } 

来自C#语言规范 :

foreach语句的编译时处理首先确定表达式的集合类型,枚举器类型和元素类型。 该决定如下:

  • 如果表达式的X类型是数组类型,则存在从X到System.Collections.IEnumerable接口的隐式引用转换(因为System.Array实现了此接口)。 集合类型是System.Collections.IEnumerable接口,枚举器类型是System.Collections.IEnumerator接口,元素类型是数组类型X的元素类型。

  • 否则,确定类型X是否具有适当的GetEnumerator方法:

    • 使用标识符GetEnumerator对类型X执行成员查找,而不使用类型参数。 如果成员查找不产生匹配,或者产生歧义,或产生不是方法组的匹配,请检查可枚举接口,如下所述。 如果成员查找产生除方法组或不匹配之外的任何内容,建议发出警告。

    • 使用生成的方法组和空参数列表执行重载解析。 如果重载决策导致没有适用的方法,导致歧义,或导致单个最佳方法但该方法是静态的或不公开的,请检查可枚举的接口,如下所述。 如果重载决策产生除明确的公共实例方法或没有适用的方法之外的任何内容,建议发出警告。

    • 如果GetEnumerator方法的返回类型E不是类,结构或接口类型,则会产生错误,并且不会采取进一步的步骤。

    • 成员查找在E上执行,标识符为Current,没有类型参数。 如果成员查找不产生匹配,则结果是错误,或者结果是除允许读取的公共实例属性之外的任何内容,产生错误并且不执行进一步的步骤。

    • 成员查找在E上执行,标识符为MoveNext,没有类型参数。 如果成员查找不产生匹配,则结果是错误,或者结果是除方法组之外的任何内容,产生错误并且不执行进一步的步骤。

    • 使用空参数列表对方法组执行重载分辨率。 如果重载决策导致没有适用的方法,导致歧义,或导致单个最佳方法但该方法是静态的或不公开的,或者其返回类型不是bool,则产生错误并且不采取进一步的步骤。

    • 集合类型为X,枚举器类型为E,元素类型为Current属性的类型。

  • 否则,检查可枚举的接口:

    • 如果只有一个类型T,从而存在从X到接口System.Collections.Generic.IEnumerable 的隐式转换,则集合类型是此接口,枚举器类型是接口System.Collections.Generic。 IEnumerator ,元素类型为T.

    • 否则,如果存在多于一个这样的类型T,则产生错误并且不采取进一步的步骤。

    • 否则,如果存在从X到System.Collections.IEnumerable接口的隐式转换,则集合类型为此接口,枚举器类型为接口System.Collections.IEnumerator,元素类型为object。

    • 否则,将产生错误,并且不会采取进一步的步骤。

不严格。 只要该类具有所需的GetEnumerator,MoveNext,Reset和Current成员,它将与foreach一起使用

不,你没有,你甚至不需要GetEnumerator方法,例如:

 class Counter { public IEnumerable Count(int max) { int i = 0; while (i <= max) { yield return i; i++; } yield break; } } 

这是这样称呼的:

 Counter cnt = new Counter(); foreach (var i in cnt.Count(6)) { Console.WriteLine(i); } 

你总是可以把它包起来,并且作为一个“可以接近”的你可以只需要一个带有正确签名的名为“GetEnumerator”的方法。

 class EnumerableAdapter { ExternalSillyClass _target; public EnumerableAdapter(ExternalSillyClass target) { _target = target; } public IEnumerable GetEnumerator(){ return _target.SomeMethodThatGivesAnEnumerator(); } } 

给定类X和方法A和B都返回IEnumerable,你可以在类上使用foreach,如下所示:

 foreach (object y in XA()) { //... } // or foreach (object y in XB()) { //... } 

据推测,A和B返回的可枚举的含义是明确的。

@Brian:不确定你是否尝试遍历方法调用或类本身的值返回,如果你想要的是类,那么通过使它成为一个可以与foreach一起使用的数组。

对于一个可以与foeach一起使用的类,它需要做的就是有一个返回的公共方法和名为GetEnumerator()的IEnumerator,就是这样:

采取以下类,它不实现IEnumerable或IEnumerator:

 public class Foo { private int[] _someInts = { 1, 2, 3, 4, 5, 6 }; public IEnumerator GetEnumerator() { foreach (var item in _someInts) { yield return item; } } } 

或者可以编写GetEnumerator()方法:

  public IEnumerator GetEnumerator() { return _someInts.GetEnumerator(); } 

在foreach中使用时(注意,使用了无包装器,只是一个类实例):

  foreach (int item in new Foo()) { Console.Write("{0,2}",item); } 

打印:

1 2 3 4 5 6

该类型仅需要具有名为GetEnumerator的公共/非静态/非generics/无参数方法,该方法应返回具有公共MoveNext方法和公共Current属性的内容。 正如我在某处回忆Eric Lippert先生所做的那样, 这是为了在价值类型的情况下适应类型安全和拳击相关性能问题的预先通用时代。

例如,这有效:

 class Test { public SomethingEnumerator GetEnumerator() { } } class SomethingEnumerator { public Something Current //could return anything { get { } } public bool MoveNext() { } } //now you can call foreach (Something thing in new Test()) //type safe { } 

然后由编译器将其翻译为:

 var enumerator = new Test().GetEnumerator(); try { Something element; //pre C# 5 while (enumerator.MoveNext()) { Something element; //post C# 5 element = (Something)enumerator.Current; //the cast! statement; } } finally { IDisposable disposable = enumerator as System.IDisposable; if (disposable != null) disposable.Dispose(); } 

从规格的8.8.4节。


值得注意的是所涉及的枚举器优先级 – 就像你有一个public GetEnumerator方法一样,那么这是foreach的默认选择,无论是谁实现它。 例如:

 class Test : IEnumerable { public SomethingEnumerator GetEnumerator() { //this one is called } IEnumerator IEnumerable.GetEnumerator() { } } 

如果您没有公共实现(即只有显式实现),那么优先级就像IEnumerator >> IEnumerator