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

时间:2008-09-24 13:47:18

标签: c# foreach ienumerable

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

11 个答案:

答案 0 :(得分:15)

与普遍看法相反,

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())
    …;

答案 1 :(得分:7)

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);
        }
    }
}

答案 2 :(得分:6)

根据MSDN

foreach (type identifier in expression) statement

表达式为:

  

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

答案 3 :(得分:4)

简答:

您需要一个名为 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# Language Specification

  

a的编译时处理   foreach声明首先确定了   集合类型,枚举器类型和   表达式的元素类型。这个   确定如下:

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

  •   
  • 否则,确定类型X是否合适   GetEnumerator方法:

         
        
    • 使用标识符GetEnumerator和no执行类型X的成员查找   类型参数。如果成员查找   没有产生匹配,或者它   产生歧义,或产生一个   匹配不是方法组,   检查可枚举的接口为   如下面所描述的。建议   会员发出警告   查找产生除a之外的任何东   方法组或不匹配。

    •   
    • 使用生成的方法组和a执行重载解析   空参数列表。如果过载   决议结果不适用   方法,导致歧义,或   得到一个最好的方法,但   该方法是静态的还是非静态的   公开,检查一个可枚举的   接口如下所述。它是   建议发出警告   如果超载分辨率产生   除了一个明确的公众之外的任何事情   实例方法或不适用   方法

    •   
    • 如果GetEnumerator方法的返回类型E不是类,   结构或接口类型,错误是   生产,没有进一步的步骤   服用。

    •   
    • 成员查询在E上执行,标识符为Current和no   类型参数。如果成员查找   产生不匹配,结果是   错误,或结果是什么   除了公共实例属性   允许阅读,产生错误   并且没有采取进一步措施。

    •   
    • 成员查询在E上执行,标识符为MoveNext,编号为no   类型参数。如果成员查找   产生不匹配,结果是   错误,或结果是什么   除了方法组,错误是   生产,没有进一步的步骤   服用。

    •   
    • 对方法组执行重载分辨率为空   参数列表。如果超载分辨率   结果没有适用的方法,   导致歧义,或导致   一种最好的方法,但该方法   是静态的还是不公开的,或者是它   返回类型不是bool,错误是   生产,没有进一步的步骤   服用。

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

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

         
        
    • 如果只有一个类型T,则存在隐式   从X转换到接口   System.Collections.Generic.IEnumerable&LT; T&gt;中   那么集合类型就是这个   接口,枚举器类型是   接口   System.Collections.Generic.IEnumerator&LT; T&gt;中   元素类型为T.

    •   
    • 否则,如果有多个这样的类型T,那么就是错误   生产,没有进一步的步骤   服用。

    •   
    • 否则,如果存在从X到。的隐式转换   System.Collections.IEnumerable   接口,然后集合类型是   这个接口,枚举器类型是   界面   System.Collections.IEnumerator,和   元素类型是对象。

    •   
    • 否则,会产生错误,不会采取进一步措施。

    •   
  •   

答案 4 :(得分:3)

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

答案 5 :(得分:2)

不,你没有,你甚至不需要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);
}

答案 6 :(得分:0)

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


class EnumerableAdapter
{
  ExternalSillyClass _target;

  public EnumerableAdapter(ExternalSillyClass target)
  {
    _target = target;
  }

  public IEnumerable GetEnumerator(){ return _target.SomeMethodThatGivesAnEnumerator(); }

}

答案 7 :(得分:0)

给定类X和方法A和B都返回IEnumerable,你可以像这样在类上使用foreach:

foreach (object y in X.A())
{
    //...
}

// or

foreach (object y in X.B())
{
   //...
}

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

答案 8 :(得分:0)

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

答案 9 :(得分:0)

对于一个可以与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

答案 10 :(得分:0)

该类型只需要一个名为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();
}

From 8.8.4 section of the spec.


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

class Test : IEnumerable<int>
{
    public SomethingEnumerator GetEnumerator()
    {
        //this one is called
    }

    IEnumerator<int> IEnumerable<int>.GetEnumerator()
    {

    }
}

如果你没有公开实现(即只有显式实现),那么优先级就像IEnumerator<T>&gt; IEnumerator