我是否正确理解了关于LINQ的谓词

时间:2012-08-04 14:16:09

标签: linq language-theory

我试图了解Predicates和LINQ。

虽然LINQ的语法开始对我有意义,但我对LINQ背后的理论有点麻烦。

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

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

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

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

在幕后,实现IEnumerable的对象的成员将根据您的谓词进行迭代并进行评估,如果使用true语法将yield return添加到新的IEnumerable对象中。

道歉,如果这看起来有点脱节,但我基本上把所有东西都扔掉了我脑子里的东西,并希望有人比我自己更了解这一点会告诉我我有什么不正确的,哪个比特是错误的,并且通常会扩展到我上面写的内容,因为我在理解这里发生的事情时遇到了一些麻烦。

2 个答案:

答案 0 :(得分: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>使其成为通用运算符 - 但是对任何条件进行过滤都比较棘手。该方法如何知道如何过滤任何类型?它显然不能 - 我们的调用代码必须告诉它正是我们所说的“过滤器” - 我们想要什么样的过滤器。所以我们给它一个第二个参数,一个函数指针,它表达我们正在追求的东西。我们称之为“谓词”,这只是一种说明返回真或假的代码块的方式。当一切都说完了,看起来有点像这样。我们将该方法重命名为“过滤器”,因为这更好地表达了我们现在要做的事情:

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截屏视频。这是一个很棒的一步一步介绍。为您提供所有基础工作,然后引导您完成每个操作员。我不能推荐它。

答案 1 :(得分:2)

  

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

我注意到的两件事是:

  1. 并非每个方法都将委托作为参数 - 例如 Skip(int count),Take(int count),Concat(IEnumerable second)等。
  2. System.Linq使用扩展方法,因为它只是LINQ的一个实现。如果我正在进行数据库查询等,我可以将其交换使用MongoDB.Driver.Linq。