【问题标题】:How does IEnumerable<T> work in backgroundIEnumerable<T> 如何在后台工作
【发布时间】:2014-09-02 16:08:39
【问题描述】:

我正在探索IEnumerable&lt;T&gt; 界面的更深入的功能。

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

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

Select 函数的结果将不会被计算(枚举),直到使用 temp 完成某些操作以允许它(例如 List&lt;int&gt; list = temp.ToList())。

然而,令我困惑的是,由于IEnumerable&lt;T&gt; 是一个接口,根据定义,它不能被实例化。那么,实际项目(在示例中为 2*x 项目)所在的集合是什么?

此外,如果我们写成 IEnumerable&lt;int&gt; temp = Enumerable.Repeat(1, 10);,那么存储 1 的底层集合是什么(数组、列表、其他)?

我似乎找不到关于这个接口的实际实现及其功能的彻底(更深入)的解释(例如,如果有一个底层集合,yield 关键字是如何工作的)。

基本上,我要求的是对IEnumerable&lt;T&gt; 的功能进行更详细的解释。

【问题讨论】:

  • 一些隐藏的框架类,它暴露了 IEnumerable 接口。这真的很重要吗?
  • 底层代码/对象返回实现 IEnumerable
  • 存储 1 的底层集合是什么(数组、列表、其他)? 它们不存储在任何地方。这就是延迟加载的重点
  • @YoryeNathan,我想知道,为了知道。我已经使用它多年,但从未真正深入。我认为这是一些运行时构造的东西,我只是不知道它的细节。

标签: c# linq oop interface ienumerable


【解决方案1】:

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

但是,如果你真的需要知道,你可以在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)

【讨论】:

  • 为什么要投反对票?这是对这个问题的一个真正有效的答案。它是一个接口,框架隐藏了实现,那又如何呢?应该给 +2 只是为了链接实现的源代码。
  • 为什么要投票?它没有回答 OP 中的任何问题。它没有解释他所说的他不理解的概念。它所说的只是他不应该关心它们(我不同意,顺便说一句),并链接到他已经说过他不理解语义的一些内容。对于已经了解 OP 明确表示他不了解其语义的行为的语义的人来说,这实际上只回答了一些问题。
【解决方案2】:

并非所有实现IEnumerable 的对象都以某种方式延迟执行。接口的 API 使延迟执行成为可能,但它并不要求它。同样有一些实现以任何方式延迟执行。

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

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

此外,如果我们写成IEnumerable&lt;int&gt; temp = Enumerable.Repeat(1, 10);,那么存储 1 的底层集合是什么(数组、列表、其他)?

不会有一个。 当您要求下一个值时,它会立即计算每个新值,之后它不会记住它。它只存储足够的信息来计算下一个值,这意味着它只需要存储元素和剩余的值的数量。

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

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 方法。那里没有什么特别有趣的东西。 )

一个更有趣的例子是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();
    }
}

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

【讨论】:

    【解决方案3】:

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

    它不在任何地方。当您迭代时,有些代码会“按需”生成单个项目,但 2*x 数字不是预先计算的。它们也不会存储在任何地方,除非您致电 ToListToArray

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

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

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

    【讨论】:

      猜你喜欢
      • 2011-05-19
      • 2010-11-06
      • 2019-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多