English 中文(简体)
Class是否需要实现IEnumerable才能使用Foreach
原标题:
  • 时间:2008-09-24 13:47:17
  •  标签:

这是在C#中,我有一个从其他DLL中使用的类。它不实现IEnumerable,但有两个方法可以传回一个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))
    …;

/编辑:如果以下方法返回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

其中表达式为:

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.

Short answer:

您需要一个具有名为GetEnumerator的方法的类,该方法将返回您已经拥有的IEnumerator。通过一个简单的包装器实现这一点:

class ForeachWrapper
{
  private IEnumerator _enumerator;

  public ForeachWrapper(Func<IEnumerator> enumerator)
  {
    _enumerator = enumerator;
  }

  public IEnumerator GetEnumerator()
  {
    return _enumerator();
  }
}

Usage:

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

From the C# Language Specification:

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.

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

    • 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.

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

不是严格意义上的。只要类具有所需的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(); }

}

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

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

// or

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

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

@Brian: Not sure you try to loop over the value return from method call or the class itself, If what you want is the class then by make it an array you can use with 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();
}

来自规范的8.8.4部分


值得注意的是所涉及的枚举器优先级-就像如果您有一个<code>public GetEnumerator</code>方法,那么无论是谁在实现它,这都是<code>foreach</code>的默认选择。例如:

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

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

    }
}

如果您没有公共实现(即只有显式实现),则优先级类似于IEnumerator<;T>>IE分子





相关问题