LINQ的Func<bool>只调用一次?

LINQ's Func<bool> is only called once?

我不知道 google 的关键字是什么...谁能给我指一个 MSDN 页面或 SO 答案来解释为什么 Foo() 只被调用一次?特别是因为 First 只有一个带有谓词的重载。这里进行了什么优化?

using System;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var foo = "Foo".First(Foo().Contains); // x 1
            var bar = "Bar".First(c => Bar().Contains(c)); // x 3
            var baz = "Baz".First(c => { return Baz().Contains(c); }); // x 3

            Console.ReadLine();
        }

        private static string Foo()
        {
            Console.WriteLine("Foo");
            return "__o";
        }

        private static string Bar()
        {
            Console.WriteLine("Bar");
            return "__r";
        }

        private static string Baz()
        {
            Console.WriteLine("Baz");
            return "__z";
        }
    }
}

编辑:

除了接受和赞成的答案(谢谢),运行 它通过 ILSpy 也帮助我直观地阐明了订单。

private static void Main(string[] args)
{
    char foo = "Foo".First(new Func<char, bool>(Program.Foo().Contains<char>));
    char bar = "Bar".First((char c) => Program.Bar().Contains(c));
    char baz = "Baz".First((char c) => Program.Baz().Contains(c));
    Console.ReadLine();
}

Foo() 只被调用一次,因为你传递给 First() 的表达式是 Foo().Contains.

要计算此表达式,Foo() 只需调用一次。

让我们考虑一下第一个和第二个片段之间的区别:

"Foo".First(Foo().Contains);

此处,First() 需要一个 Func<char, bool> 参数。 Foo() 被调用(一次)并对结果执行 Contains 的成员访问。该成员访问的结果确实是一个 Func<char, bool>,因此代码是有效的并且该委托被传递给 First(),它继续为 "Foo" 中的每个字符调用它。请注意,我们在这里完成了调用 Foo(),因为调用委托并不意味着我们必须再次计算 Foo()

"Bar".First(c => Bar().Contains(c));

这里,传递给First()Func<char, bool>是lambda表达式c => Bar().Contains(c)First() 将继续为 "Bar" 中的每个字符调用该委托。 lambda 表达式的 "body" 在每次调用时执行,这导致 Bar() 被调用三次。

需要拆分直接看原因:

var foo = "Foo".First(Foo().Contains);

基本上是:

string foo = Foo();                    // only called once
Func<char, bool> func = foo.Contains;  // = "__o".Contains
var foo = "Foo".First(func);

如您现在所见,Foo 仅被调用一次,而 returns “__o”。然后 First 所需的 Func<char, bool> 委托从该字符串中获取,这基本上意味着它是字符串“__o”上的 Contains 而不是方法 Foo , 因此 "Foo" 只打印一次。

在其他两种情况下,您传入一个 Lambda 表达式,然后为每个字符调用该表达式 - 拆分方式与上述相同,这只是:

Func<char, bool> func = c => Bar().Contains(c);
var bar = "Bar".First(func);

这里没有调用Bar来构造Func<char, bool>,因为它只调用了"inside"它的主体,这就是为什么每次调用都会调用Bar其中 Func<char, bool>.

在所有这三种情况下,您都传递了一个接受字符和 returns 布尔值的函数。在所有情况下,该函数都将针对字符串中的每个字符执行。不同之处在于这些函数的定义方式。

var foo = "Foo".First(Foo().Contains); // x 1

在这里,您将该函数定义为属于 Foo() 返回的对象的 Contains 函数。要获得 Foo() 只需执行一次。

var bar = "Bar".First(c => Bar().Contains(c)); // x 3
var baz = "Baz".First(c => { return Baz().Contains(c); }); // x 3

在这两种情况下,您定义的函数最终会在函数调用中调用 Bar()Baz()