4

我试图了解谓词和 LINQ。

虽然 LINQ 的语法开始对我有意义,但我在 LINQ 背后的理论方面遇到了一些麻烦。

这是我到目前为止所拥有的。在设计 LINQ 时,Microsoft 没有创建一个新接口来定义可以使用 LINQ 查询的任何对象都需要实现的每个成员,而是决定采用现有的类 IEnumerable,并使用扩展方法扩展该类。

我想我了解扩展方法。扩展方法是静态类中的静态方法。传入此方法的第一个参数与this参数一起传入,并定义要扩展的类型。然后,与扩展方法在同一命名空间内的任何这种类型的实例都可以使用该方法。

因此,微软在 System.LINQ 命名空间内创建了许多扩展 IEnumerable 的扩展方法,任何使用 System.LINQ 命名空间并包含实现 IEnumerable 的对象的类都可以使用这些扩展方法来查询该对象。这些扩展方法中的每一个都将委托作为其第二个参数。

关于where,where是扩展 IEnumerable 并返回实现 IEnumerable 的新对象的扩展方法。下一个参数where采用 Func 类型(泛型 func)的谓词(返回布尔值的方法)。这是一个委托,它返回 true 或 false,最多可以接受 16 个参数。但是,不必编写满足此条件的方法,而是创建 Func 类型的实例并将其指向您的方法,并将此变量传递给where方法,C# 允许您即时编写此方法。您在构建 LINQ 查询时放在单词后面的所有内容都将where成为您的谓词。

在幕后,实现 IEnumerable 的对象的成员被迭代并针对您的谓词进行评估,并且 iftrue使用yield return语法添加到新的 IEnumerable 对象中。

抱歉,如果这看起来有点脱节,但我基本上已经把所有事情都从我的大脑中甩了出来,并希望比我自己更了解这一点的人会过来告诉我我的哪些位是正确的,哪些位是错误的并且通常扩展我上面写的内容,因为我在正确理解这里发生的事情时遇到了一些麻烦。

4

2 回答 2

4

虽然我不完全确定你在追求什么,但我认为你在很大程度上是有权利的。如果我正确阅读了您的问题,那么您实际上正在考虑两件事:扩展方法和谓词。

以下是可能有助于理解的内容:您基本上可以自己实现 Where 运算符,一步一步地查看所有部分的位置。一旦您知道引擎盖下的内容,它似乎就不那么神奇了。

假设我们有一系列事物,我们想编写一个方法来帮助我们找出其中哪些事物很棒。这是我们可以做到的一种方法:

static IEnumerable<Thing> ThingsThatAreAwesome(IEnumerable<Thing> things){
    List<Thing> ret;
    foreach (Thing thing in things) {
        if (thing.IsAwesome)
            ret.Add(thing);
    }

    return ret;
}

然后我们会这样称呼:

List<Thing> myThings;
List<Thing> myAwesomeThings = ThingsThatAreAwesome(myThings);

所以这很热心。我们只是遍历我们的列表,看看其中哪些很棒,然后返回符合我们很棒标准的那些。但从语义上讲,它并没有真正为我们做这件事——我们很棒的过滤器太棒了,我们希望能够走到一个事物列表并调用我们的操作符,就好像它是 IEnumerable 本身的实例方法一样.

这就是扩展方法的用武之地。通过一些编译器技巧,它们使我们能够“扩展”类型。所以就像你说的,通过将“this”放在我们方法的 IEnumerable 参数前面,我们现在可以走到我们的列表并要求它像这样过滤自己:

List<Thing> myAwesomeThings = myThings.ThingsThatAreAwesome();

所以 - 这就是扩展方法适合的地方。接下来是“谓词”。

所以我们得到了我们非常棒的过滤器,它很棒,但是我们有一个大脑爆炸:稍微抽象一下,我们刚刚编写的那个方法可以用来过滤任何东西。不仅仅是 Thing 对象的列表,也不仅仅是过滤很棒的东西。

让它适用于任何类型都相当容易,我们只是用IEnumerable<T>, 而不是IEnumerable<Thing>- 让它成为一个通用运算符,但让它根据任何标准过滤就更棘手了。该方法应该如何知道如何过滤任何类型?它显然不能——我们的调用代码必须准确地告诉它我们所说的“过滤器”是什么意思——我们想要什么样的过滤器。所以我们给它第二个参数,一个函数指针,它表达了我们所追求的。我们将其称为“谓词”,这只是一种表示返回真或假的代码块的方式。当一切都说完了,它看起来有点像这样。我们将把方法重命名为“Filter”,因为这样可以更好地表达我们现在要做的事情:

static IEnumerable<T> Filter(this IEnumerable<T> list, Func<T,bool> predicate) {
    foreach (T item in list) {
        if (predicate(item))
            yield return item;
    }
}

您可以看到,我们实际上并没有做任何与之前的 Awesome 过滤器方法不同的事情——我们仍然只是迭代一个列表,执行某种检查,并返回通过该检查的项目。但是我们已经给自己提供了一种调用代码的方法来准确地表达“检查”应该是什么。

我们基本上仍然只做两件事:迭代列表,并对列表中的每个项目进行某种检查——通过检查的项目会被传回。除了现在该方法并不真正知道它正在运行的检查是什么样的 - 我们告诉它,将那段代码作为参数传递,我们的谓词,而不是将其硬编码到方法本身中。我们让调用者来决定他们希望他们的过滤标准是什么。

所以此时,我们基本上已经有了 LINQ Where 运算符——我们现在可以对任何类型的集合运行查询,所有这些都使用相同的方法。如果你还没有弄乱 lambdas,不要担心——只要知道这是一种非常简洁的代码表达方式,在这种情况下,它告诉我们的过滤器方法我们想要过滤什么:

List<Thing> myThings;
List<Cats>  myCats;

var myAwesomeThings = myThings.Filter(thing => thing.IsAwesome);
var myCrazyCats = myCats.Filter(cat => cat.IsCrazy);

foreach (var thing in myAwesomeThings){
    Console.WriteLine("This thing is awesome! {0}", thing);
}

foreach (var cat in myCrazyCats){
    Console.WriteLine("This cat is crazy! {0}", cat);
}

我希望这有助于巩固一些概念——但如果你真的想深入了解 LINQ,你会想要退出 TekPub 的Mastering LINQ截屏视频。这是一个很棒的逐步介绍。为您提供所有基础知识,然后引导您了解几乎所有操作员。我不能推荐它。

于 2012-08-04T23:23:42.237 回答
2

因此,微软在 System.LINQ 命名空间内创建了许多扩展 IEnumerable 的扩展方法,任何使用 System.LINQ 命名空间并包含实现 IEnumerable 的对象的类都可以使用这些扩展方法来查询该对象。这些扩展方法中的每一个都将委托作为其第二个参数。

我要注意的两件事是:

  1. 并非每个方法都将委托作为参数 - 例如 Skip(int count)、Take (int count)、Concat(IEnumerable second) 等。
  2. System.Linq 使用扩展方法,因为它只是 LINQ 的一种实现。如果我正在执行数据库查询等,我可以将其换成使用 MongoDB.Driver.Linq。
于 2012-08-05T05:47:37.863 回答