Class是否需要实现IEnumerable才能使用Foreach
这是在C#中,我有一个从其他DLL使用的类。它没有实现IEnumerable,但是有2种方法可以传回IEnumerator。有没有一种方法可以在这些上使用foreach循环。我正在使用的课程是密封的。
解决方案
与普遍的看法相反," foreach"不需要" IEnumerable"。它所需要的只是一个方法GetEnumerator,该方法返回具有方法MoveNext和具有适当签名的get属性Current的任何对象。
/编辑:但是,就我们而言,我们很不走运。我们可以轻松包装对象,使其可枚举:
class EnumerableWrapper { private readonly TheObjectType obj; public EnumerableWrapper(TheObjectType obj) { this.obj = obj; } public IEnumerator<YourType> GetEnumerator() { return obj.TheMethodReturningTheIEnumerator(); } } // Called like this: foreach (var xyz in new EnumerableWrapper(yourObj)) …;
/ EDIT:如果该方法返回一个IEnumerator,则由几个人提出的以下方法将不起作用:
foreach (var yz in yourObj.MethodA()) …;
我们总是可以包装它,并且除了要成为"可实现的"之外,我们只需要具有正确签名的方法" GetEnumerator"。
class EnumerableAdapter { ExternalSillyClass _target; public EnumerableAdapter(ExternalSillyClass target) { _target = target; } public IEnumerable GetEnumerator(){ return _target.SomeMethodThatGivesAnEnumerator(); } }
不严格。只要该类具有必需的GetEnumerator,MoveNext,Reset和Current成员,它将与foreach一起使用
给定类X具有方法A和B都返回IEnumerable的方法,我们可以在此类上使用foreach,如下所示:
foreach (object y in X.A()) { //... } // or foreach (object y in X.B()) { //... }
大概A和B返回的可枚举的含义是明确定义的。
不,我们不需要,甚至不需要GetEnumerator方法,例如:
class Counter { public IEnumerable<int> 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); }
根据MSDN:
foreach (type identifier in expression) statement
表达式是:
Object collection or array expression. The type of the collection element must be convertible to the identifier type. Do not use an expression that evaluates to null. Evaluates to a type that implements IEnumerable or a type that declares a GetEnumerator method. In the latter case, GetEnumerator should either return a type that implements IEnumerator or declares all the methods defined in IEnumerator.
回复:如果foreach不需要显式接口协定,它是否使用反射找到GetEnumerator?
(因为我的声誉不够高,所以我无法发表评论。)
如果我们暗示运行时反射,则否。它全部完成了编译时间,另一个鲜为人知的事实是,它还检查可能实现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); } } }
简短答案:
我们需要一个带有名为GetEnumerator的方法的类,该方法将返回我们已经拥有的IEnumerator。使用一个简单的包装器即可实现此目的:
class ForeachWrapper { private IEnumerator _enumerator; public ForeachWrapper(Func<IEnumerator> enumerator) { _enumerator = enumerator; } public IEnumerator GetEnumerator() { return _enumerator(); } }
用法:
foreach (var element in new ForeachWrapper(x => myClass.MyEnumerator())) { ... }
从C#语言规范中:
The compile-time processing of a foreach statement first determines the collection type, enumerator type and element type of the expression. This determination proceeds as follows: If the type X of expression is an array type then there is an implicit reference conversion from X to the System.Collections.IEnumerable interface (since System.Array implements this interface). The collection type is the System.Collections.IEnumerable interface, the enumerator type is the System.Collections.IEnumerator interface and the element type is the element type of the array type X. Otherwise, determine whether the type X has an appropriate GetEnumerator method: Perform member lookup on the type X with identifier GetEnumerator and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match. Perform overload resolution using the resulting method group and an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, check for an enumerable interface as described below. It is recommended that a warning be issued if overload resolution produces anything except an unambiguous public instance method or no applicable methods. If the return type E of the GetEnumerator method is not a class, struct or interface type, an error is produced and no further steps are taken. Member lookup is performed on E with the identifier Current and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken. Member lookup is performed on E with the identifier MoveNext and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken. Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not bool, an error is produced and no further steps are taken. The collection type is X, the enumerator type is E, and the element type is the type of the Current property. Otherwise, check for an enumerable interface: If there is exactly one type T such that there is an implicit conversion from X to the interface System.Collections.Generic.IEnumerable<T>, then the collection type is this interface, the enumerator type is the interface System.Collections.Generic.IEnumerator<T>, and the element type is T. Otherwise, if there is more than one such type T, then an error is produced and no further steps are taken. Otherwise, if there is an implicit conversion from X to the System.Collections.IEnumerable interface, then the collection type is this interface, the enumerator type is the interface System.Collections.IEnumerator, and the element type is object. Otherwise, an error is produced and no further steps are taken.
@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