【问题标题】:At what point is a LINQ data source determined?什么时候确定 LINQ 数据源?
【发布时间】:2017-05-18 18:36:53
【问题描述】:

鉴于以下两个 LINQ 示例,LINQ 数据源是在什么时候确定的?

int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
IEnumerable<int> linqToOjects = numbers.Where(x => true);

XElement root = XElement.Load("PurchaseOrder.xml");
IEnumerable<XElement> linqToXML = root.Elements("Address").Where(x => true);

我的理解是,用于查询这两个不同数据源的底层代码位于 LINQ 方法生成的 IEnumerable 对象中。

我的问题是,究竟是在什么时候确定将生成代码以使用 Linq To Objects 库还是 Linq To XML 库?

我假设用于查询这些数据源的底层代码(实际执行查询数据工作的代码)存在于它们自己的库中,并且依赖于数据源而被调用。我查看了 https://referencesource.microsoft.com/ 以查看 Where 子句/扩展方法的代码,认为对所需提供程序的调用可能在其中,但它似乎是通用的。

进入 IEnumerable 的魔法是如何确定的?

【问题讨论】:

  • IEnumerable 直到它更改为“已确定”类型(例如当您在其上调用 .ToList().ToArray() 时)才确定。但IEnumerable 本身从未确定。
  • 谢谢 - 我应该把它写成 IEnumerable 实例。
  • 唯一重要的是当您使用 IQueryable 时。否则,并不是所有的对象都是 linq。 (但实际使用的实现是在编译时根据您提供的输入类和解析的扩展方法来选择的。)

标签: c# linq ienumerable extension-methods


【解决方案1】:

立即确定“数据源”。例如,在您的第一个示例中,Where 的返回值是一个实现 IEnumerable&lt;int&gt;(特别是 Enumerable.WhereArrayIterator&lt;int&gt; 类)的对象,该对象依赖于 numbers 对象(存储为字段)。而第二个例子中Where的返回值是一个依赖于xml元素对象的可枚举对象。因此,即使在您开始枚举之前,生成的可枚举对象就知道从哪里获取数据。

【讨论】:

  • 当你说生成的可枚举知道从哪里获取数据时,它是否也知道如何在该点获取数据,如在用于实际查询数据(存储为字段)的可枚举代码中,因为我想用于查询数组和 xml 对象的设施非常不同。
  • @Brummy 它并不需要知道如何。该数据源负责使用IEnumerable 接口公开其数据,因此所有Where 方法需要做的就是使用该接口并允许底层数据源执行它需要执行的任何操作来获取数据.
【解决方案2】:

我的问题是,究竟是在什么时候确定代码是否 将生成使用 Linq To Objects 库或 Linq To XML 库?

我认为没有代码生成。 LINQ 只使用数据源enumerator

你有一个实现IEnumerable的类

公开枚举器,它支持一个简单的迭代 指定类型的集合。

所以你可以使用GetEnumerator的方法。

返回一个遍历集合的枚举器。

而这一切 LINQ 都需要工作,enumerator

在您的示例中,您使用 Where LINQ 扩展方法来应用一些过滤器。

IEnumerable<T> Where(this IEnumerable<T> source, Func<T, bool> predicate)

在实现中我们需要:
- 获取枚举器 (source.GetEnumerator())
- 遍历集合并应用过滤器(谓词)

Enumerable reference source 你有Where方法的实现。您可以看到他对数组 (TSource[]) 和列表 (List) 使用了一些特定的实现,但他对实现 IEnumerable 的所有其他类使用了WhereEnumerableIterator。 所以没有代码生成,代码就在那里。

我想你可以理解WhereEnumerableIterator类的实现,你只需要先理解如何实现IEnumerator

Here可以看到MoveNext的实现。他们调用 source.GetEnumerator(),然后遍历集合 (enumerator.MoveNext()) 并应用过滤器 (predicate(item))。

public override bool MoveNext() {
    switch (state) {
        case 1:
            enumerator = source.GetEnumerator();
            state = 2;
            goto case 2;
        case 2:
            while (enumerator.MoveNext()) {
                TSource item = enumerator.Current;
                if (predicate(item)) {
                    current = item;
                    return true;
                }
            }
            Dispose();
            break;
    }
    return false;
}  

XContainer.GetElement 使用 yield 关键字返回一个 IEnumerable。

当您在语句中使用 yield 关键字时,您表明 它出现在其中的方法、运算符或 get 访问器是一个迭代器。 使用 yield 来定义迭代器消除了对显式的需要 额外类(保存枚举状态的类,请参阅 IEnumerator 为例)当您实现 IEnumerable 和 自定义集合类型的 IEnumerator 模式。

感谢yield关键字的魔力,我们可以得到一个IEnumerable,我们可以枚举集合。这是 LINQ 唯一需要的。

【讨论】:

  • 感谢您的详细回复,我只是在看那个源代码!我是否认为从GetElement 方法(XContainer)返回的对象应该有一个 GetEnumerator 方法?翻阅微软参考代码我看不到它。
  • 我现在明白你的意思了。 XContainer.GetElement 方法返回的对象是一个 IEnumerable,所以,我们可以这样做:root.Elements("Address").GetEnumerator()。 LINQ 使用这个枚举器。但是,GetElement 方法如何返回一个 IEnumerable?使用 yield 关键字。我修改了我的答案给你看。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-27
相关资源
最近更新 更多