这是在 C# 中,我有一个从其他人的 DLL 中使用的类。它没有实现 IEnumerable,但有 2 个方法可以传回 IEnumerator。有没有办法可以在这些上使用 foreach 循环。我使用的类是密封的。
11 回答
foreach
不需要,与IEnumerable
流行的看法相反。它所需要的只是一个方法,该方法返回具有该方法和具有适当签名的 get-property的GetEnumerator
任何对象。MoveNext
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))
…;
/编辑:如果该方法返回一个,则由几个人提出的以下方法不起作用IEnumerator
:
foreach (var yz in yourObj.MethodA())
…;
回复:如果 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);
}
}
}
根据MSDN:
foreach (type identifier in expression) statement
其中表达式为:
对象集合或数组表达式。集合元素的类型必须可转换为标识符类型。不要使用计算结果为 null 的表达式。求值为实现 IEnumerable 的类型或声明 GetEnumerator 方法的类型。在后一种情况下,GetEnumerator 应返回实现 IEnumerator 的类型或声明 IEnumerator 中定义的所有方法。
简短的回答:
您需要一个具有名为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# 语言规范:
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<T> 的隐式转换,则集合类型是此接口,枚举器类型是接口 System.Collections.Generic。 IEnumerator<T>,元素类型为 T。
否则,如果存在不止一个这样的类型 T,则会产生错误并且不采取进一步的步骤。
否则,如果存在从 X 到 System.Collections.IEnumerable 接口的隐式转换,则集合类型为该接口,枚举器类型为接口 System.Collections.IEnumerator,元素类型为对象。
否则,将产生错误并且不采取进一步的步骤。
不严格。只要该类具有所需的 GetEnumerator、MoveNext、Reset 和 Current 成员,它将与 foreach 一起使用
不,您不需要,甚至不需要 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);
}
你总是可以包装它,并且作为“可访问”的旁白,你只需要一个名为“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 X.A())
{
//...
}
// or
foreach (object y in X.B())
{
//...
}
大概 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
该类型只需要有一个名为 public/non-static/non-generic/parameterless 的方法GetEnumerator
,它应该返回具有公共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();
}
值得注意的是所涉及的枚举器优先级 - 如果您有一个public GetEnumerator
方法,那么foreach
无论谁在实现它,这都是默认选择。例如:
class Test : IEnumerable<int>
{
public SomethingEnumerator GetEnumerator()
{
//this one is called
}
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
}
}
(如果您没有公共实现(即只有显式实现),则优先级类似于IEnumerator<T>
> IEnumerator
。)