为什么这三段 LINQ 代码会产生不同(或错误)的结果?

发布于 2024-10-31 09:57:36 字数 2551 浏览 0 评论 0原文

以下是一些示例数据:

List<Book> books = new List<Book>()
{
    new Book(){Title = "artemis fowl: the time paradox", Pages = 380},
    new Book(){Title = "the lieutenant", Pages = 258},
    new Book(){Title = "the wheel of time", Pages = 1032},
    new Book(){Title = "ender's game", Pages = 404},
    new Book(){Title = "the sphere",  Pages = 657}
};  

背景:

上面使用了 Book 类的简化版本。当然,它会包含许多字段。我的最终目标是允许用户执行“高级”搜索,允许用户指定任何字段,并进一步允许用户使用布尔代数为特定字段指定关键字。

例如:在标题搜索文本框中:+ (cake | Pastry) + ~demon

上面的意思是:查找标题中包含单词“the”、“cake”或“pastry”的所有书籍,并且没有‘恶魔’这个词。

问题

一步一步就能找到最终的解决方案。所以我最初有以下代码:

List<Func<Book, bool>> fs = new List<Func<Book, bool>>()
{
    b => b.Title.Contains("me"),
    b => b.Title.Contains("the")
};

var q2 = from b in books select b;
foreach (var f in fs)
    q2 = q2.Where(f);

foreach (Book b in q2)
{
    Console.WriteLine("Title:\t\t{0}\nPages:\t\t{1}\n",
                      b.Title, b.Pages);
}

上面的代码工作正常。它查找标题中包含“the”和“me”的书籍。

阶段 2

现在上面的过滤器的类型为 Func<Book, bool>。该类将是实体框架生成的类,我不想在 UI 层中使用,其中将输入搜索短语并生成搜索过滤器以传递到 BLL。

所以我有以下三个尝试:

var q = from b in books select b;

List<Func<string, bool>> filters  = new List<Func<string, bool>>()
{
    s => s.Contains("me"),
    s => s.Contains("the"),
};

//This works...
for (int i = 0; i != filters.Count; ++i)
{
    Func<string, bool> daF = filters[i];
    q = q.Where(b => (daF(b.Title)));
}     

            //This produces an exception...
            //Due to index in query?
//            for (int i = 0; i != filters.Count; ++i)
//            {
//                q = q.Where(b => ((filters[i])(b.Title)));
//            }

            //This runs but doesn't produce the proper output
//            foreach (Func<string, bool> filter in filters)
//              q = q.Where(b => filter(b.Title));

foreach (Book b in q)
{
    Console.WriteLine("Title:\t\t{0}\nPages:\t\t{1}\n",
                      b.Title, b.Pages);
}

第一个注释掉的部分触发索引器超出范围异常,指出 i 的值为 2。

第二个注释掉的部分运行并产生输出,但它打印出 5 本书中的 4 本书……除了题为“安德的游戏”的书。这是不对的……

所以,读完我的帖子,我发现我无法改掉解释每一个小细节的坏习惯……

所以就这样吧。请解释为什么输出不同。我想你可能会暗示我当前的“解决方案”可能会得到改进。

Here is some sample data:

List<Book> books = new List<Book>()
{
    new Book(){Title = "artemis fowl: the time paradox", Pages = 380},
    new Book(){Title = "the lieutenant", Pages = 258},
    new Book(){Title = "the wheel of time", Pages = 1032},
    new Book(){Title = "ender's game", Pages = 404},
    new Book(){Title = "the sphere",  Pages = 657}
};  

Background:

The above uses a simplified version of a Book class. It would, of course, contain many fields. My end goal is to allow the user to perform an 'advanced' search allowing the user to specify any field and further allow the user to specify keywords using boolean algebra for a particular field.

eg: In a title search text box: the + (cake | pastry) + ~demon

The above would mean: Find all books that, in the title, have the words 'the', either of 'cake' or 'pastry', and does not have the word 'demon'.

Problem:

Baby steps will lead to the final solution. So I initially had the following code:

List<Func<Book, bool>> fs = new List<Func<Book, bool>>()
{
    b => b.Title.Contains("me"),
    b => b.Title.Contains("the")
};

var q2 = from b in books select b;
foreach (var f in fs)
    q2 = q2.Where(f);

foreach (Book b in q2)
{
    Console.WriteLine("Title:\t\t{0}\nPages:\t\t{1}\n",
                      b.Title, b.Pages);
}

The above code works fine. It looks for books that contain 'the' AND 'me' in the title.

Phase 2

Now the above filter is of type Func<Book, bool>. That class will be an Entity Framework generated class and I don't want to use in my UI layer where the search phrase will be input and search filters will be generated to be passed on to the BLL.

So I have the following three attempts:

var q = from b in books select b;

List<Func<string, bool>> filters  = new List<Func<string, bool>>()
{
    s => s.Contains("me"),
    s => s.Contains("the"),
};

//This works...
for (int i = 0; i != filters.Count; ++i)
{
    Func<string, bool> daF = filters[i];
    q = q.Where(b => (daF(b.Title)));
}     

            //This produces an exception...
            //Due to index in query?
//            for (int i = 0; i != filters.Count; ++i)
//            {
//                q = q.Where(b => ((filters[i])(b.Title)));
//            }

            //This runs but doesn't produce the proper output
//            foreach (Func<string, bool> filter in filters)
//              q = q.Where(b => filter(b.Title));

foreach (Book b in q)
{
    Console.WriteLine("Title:\t\t{0}\nPages:\t\t{1}\n",
                      b.Title, b.Pages);
}

The first commented-out piece fires an indexer out of range exception stating that the value of i is 2.

The second commented-out piece runs and produces output, but it prints out FOUR of the 5 books... all EXCEPT the book titled "ender's game". That's not right...

So, reading over my post, I see that I could not reign in my bad habit of explaining every little detail...

So there you go. Please explain why the differing outputs. And I guess you could hint on likely improvements to my current 'solution'.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

凤舞天涯 2024-11-07 09:57:36

由于我们在这里使用 LINQ to Objects,因此您应该能够使用 All()。那么你就不需要循环了。

var query = books.Where(book => filters.All(filter => filter(book.Title)));

这相当于:

var query = from book in books
            where filters.All(filter => filter(book.Title))
            select book;

至于为什么其他尝试不起作用,您是 关闭循环变量。通常,因此在循环中使用 lambda 函数时应该小心。简单的修复方法是声明一个在 lambda 中使用的单独变量。请注意,您实际上在第一个查询中间接执行了此操作。但是,您根本不需要循环,而应该使用上面的查询之一。

for (int i = 0; i != filters.Count; ++i)
{
    var index = i;
    q = q.Where(b => filters[index](b.Title));
}

foreach (Func<string, bool> f in filters)
{
    var filter = f;
    q = q.Where(b => filter(b.Title));
}

Since we're using LINQ to Objects here, you should be able to use All(). Then you won't need to loop.

var query = books.Where(book => filters.All(filter => filter(book.Title)));

Which is equivalent to:

var query = from book in books
            where filters.All(filter => filter(book.Title))
            select book;

As for why the other attempts doesn't work, you are closing over the loop variable. Generally, you should be careful when using lambda functions in loops due to this. The simple fix is to declare a separate variable that you use in your lambdas. Notice that you actually did this indirectly in your first query. However you shouldn't need loops at all and should use one of the queries above.

for (int i = 0; i != filters.Count; ++i)
{
    var index = i;
    q = q.Where(b => filters[index](b.Title));
}

foreach (Func<string, bool> f in filters)
{
    var filter = f;
    q = q.Where(b => filter(b.Title));
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文