IEnumerable <t>如何在后台工作</t>

时间:2014-09-02 16:08:39

标签: c# linq oop interface ienumerable

我正在徘徊IEnumerable<T>界面的更深入的功能。

基本上,它作为执行中的中间步骤。例如,如果你写:

IEnumerable<int> temp = new int[]{1,2,3}.Select(x => 2*x);

Select函数的结果将不会被计算(枚举),直到用temp来完成某事(例如List<int> list = temp.ToList())。

然而,让我感到困惑的是,由于IEnumerable<T>是一个接口,因此根据定义,它不能被实例化。那么,实际项目(在示例2*x项目中)所在的集合是什么?

此外,如果我们要编写IEnumerable<int> temp = Enumerable.Repeat(1, 10);,那么存储1的底层集合是什么(数组,列表,其他东西)?

我似乎无法找到关于此接口的实际实现及其功能的详尽(更深入)解释(例如,如果存在基础集合,yield关键字如何工作)

基本上,我要求的是对IEnumerable<T>的功能进行更详尽的解释。

3 个答案:

答案 0 :(得分:5)

实施无关紧要。所有这些(LINQ)方法都返回IEnumerable<T>,接口成员是您可以访问的唯一成员,这应该足以使用它们。

但是,如果你真的必须知道,你可以在http://sourceof.net上找到实际的实现。

但是,对于某些方法,您将无法找到显式类声明,因为其中一些使用yield return,这意味着编译期间编译器会生成正确的类(带状态机)。例如Enumerable.Repeat以这种方式实现:

public static IEnumerable<int> Range(int start, int count) {
    long max = ((long)start) + count - 1;
    if (count < 0 || max > Int32.MaxValue)
        throw Error.ArgumentOutOfRange("count");
    return RangeIterator(start, count); 
}

static IEnumerable<int> RangeIterator(int start, int count) {
    for (int i = 0; i < count; i++)
        yield return start + i;
}

您可以在MSDN上阅读有关该内容的更多信息:Iterators (C# and Visual Basic)

答案 1 :(得分:4)

并非所有实现IEnumerable的对象都以某种方式延迟执行。界面的API使可能推迟执行,但它并不要求它。同样的实现以任何方式推迟执行。

  

那么,实际项目(在示例2 * x项目中)所在的集合是什么?

没有。每当请求下一个值时,它会计算一个值 on demand ,将其提供给调用者,然后忘记该值。它并没有将它存储在其他地方。

  

此外,如果我们要编写IEnumerable<int> temp = Enumerable.Repeat(1, 10);,那么存储1的底层集合是什么(数组,列表,其他东西)?

不会是一个人。当您要求下一个值时,它会立即计算每个新值,并且之后不会再记住它。它只存储足够的信息以便能够计算下一个值,这意味着它只需要存储元素和剩余的值来产生。

虽然实际的.NET实现将使用更简洁的方法来创建这样的类型,但创建一个延迟执行的枚举并不是特别困难。这样做即使是漫长的道路也比艰难更乏味。您只需计算迭代器的MoveNext方法中的下一个值。在您要求的示例Repeat中,这很简单,因为您只需要计算,如果有另一个值,而不是它是什么:

public class Repeater<T> : IEnumerator<T>
{
    private int count;
    private T element;

    public Repeater(T element, int count)
    {
        this.element = element;
        this.count = count;
    }
    public T Current { get { return element; } }

    object IEnumerator.Current
    {
        get { return Current; }
    }

    public void Dispose() { }

    public bool MoveNext()
    {
        if (count > 0)
        {
            count--;
            return true;
        }
        else
            return false;
    }

    public void Reset()
    {
        throw new NotSupportedException();
    }
}

(我省略了只返回此类型的新实例的IEnumerable类型,或者创建了一个可枚举的新实例的静态Repeat方法。没有&#39;看到那里有什么特别有趣的东西。)

一个稍微有趣的例子就像Count

public class Counter : IEnumerator<int>
{
    private int remaining;

    public Counter(int start, int count)
    {
        Current = start;
        this.remaining = count;
    }
    public int Current { get; private set; }

    object IEnumerator.Current
    {
        get { return Current; }
    }

    public void Dispose() { }

    public bool MoveNext()
    {
        if (remaining > 0)
        {
            remaining--;
            Current++;
            return true;
        }
        else
            return false;
    }

    public void Reset()
    {
        throw new NotSupportedException();
    }
}

在这里,我们不仅计算我们是否有另一个值,而是每次向我们请求新值时,下一个值是什么。

答案 2 :(得分:2)

  

那么,实际项目(在示例2 * x项目中)所在的集合是什么?

它不在任何地方居住。有代码可以按需生成单个项目&#34;迭代时,2*x数字不是预先计算的。它们也不会存储在任何地方,除非您拨打ToListToArray

  

此外,如果我们要编写IEnumerable temp = Enumerable.Repeat(1,10);,那么存储1s的底层集合(数组,列表,其他东西)是什么?

同样的图片在这里:IEnumerable的返回实现不公开,并且按需返回其项目,而不将它们存储在任何地方。

C#编译器提供了一种实现IEnumerable的便捷方式,而无需为其定义类。您所需要的只是将方法返回类型声明为IEnumerable<T>,并使用yield return根据需要提供值。