题
这是在C#我有一个类,我使用一些其他的DLL。它并不实施类型,但有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<YourType> GetEnumerator() {
return obj.TheMethodReturningTheIEnumerator();
}
}
// Called like this:
foreach (var xyz in new EnumerableWrapper(yourObj))
…;
/编辑:如果方法返回IEnumerator
,则以下几个人提出的方法不有效:
foreach (var yz in yourObj.MethodA())
…;
其他提示
Re:如果foreach不需要显式接口契约,它是否使用反射找到GetEnumerator?
(我无法发表评论,因为我没有足够高的声誉。)
如果你暗示运行时反射,那么没有。它完成所有编译时间,另一个鲜为人知的事实是它还检查可能实现IEnumerator的返回对象是否是一次性的。
要查看此操作,请考虑此(runnable)代码段。
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的 系统。集合。类型 接口(因为系统。阵列 实现这一接口)。的 收集类型 系统。集合。类型 接口,枚举的类型是 系统。集合。IEnumerator 接口和元件的类型是 元素类型阵型X。
否则,确定是否型X有一个合适的 GetEnumerator方法:
执行成员查找的类型X与标识符GetEnumerator和没有 类型参数。如果件的查找 不会产生相匹配,或者它 产生歧义,或产生一个 比赛不是一个种群, 检查一枚举的接口 如下所述。它建议 警告发出的,如果成员 查找产生任何东西除了一个 法集团或者不相匹配。
执行载的决议使用所得的方法小组和一个 空参数清单。如果超载 决议的结果在没有适用 方法,结果在一含糊不清,或 结果在一个单一的最佳方法,但 这种方法是静态的或者不 公共检查的可枚举 接口,如下所述。它是 建议一个警告可以发出的 如果载的决议产生 任何东西除了一个明确的公共 实例法或不适用 方法。
如果返回的E型的GetEnumerator方法不是一类, 结构或口类型、一个错误是 产生并没有进一步的步骤 采取的。
成员执行查找有关电子标识符目前并没有 类型参数。如果件的查找 产生的不匹配,结果是 错误,或结果是什么 除了公共财产实例 允许阅读,将产生错误 并没有进一步采取措施。
成员执行查找有关电子标识符MoveNext和没有 类型参数。如果件的查找 产生的不匹配,结果是 错误,或结果是什么 除了一个方法小组、一个错误是 产生并没有进一步的步骤 采取的。
载的决议是执行在方法组有一个空的 参数清单。如果载的决议 结果在没有适用的方法, 结果在一含糊,或者在结果 一个单一的最佳方法,但这种方法 是静态的或者不公开,或者它 回归类型不是布尔,一个错误是 产生并没有进一步的步骤 采取的。
收集类型X,枚举的类型是电子和元素 类型是电流 财产。
否则,检查可枚举的界面:
如果有一个T类型,例如有一个隐含的 转换从X口 系统。集合。通用的。类型<T>, 然后收集型就是这个 接口,枚举的类型是 接口 系统。集合。通用的。IEnumerator<T>, 和元素类型是T
否则,如果有多于一个这种类型的T,然后一个错误是 产生并没有进一步的步骤 采取的。
否则,如果有一个隐含的转换从X的 系统。集合。类型 接口,然后收集类型 这个界面,该枚举的类型是 该接口 系统。集合。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);
}
你总是可以把它包起来,并且作为一个旁边是<!> quot; foreachable <!> quot;你只需要一个名为<!>的方法; GetEnumerator <!> quot;有适当的签名。
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
该类型只需要具有名为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>
<!> gt; IEnumerator
。)