LINQ Func <bool> вызывается только один раз?
Я потерял ключевые слова для 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";
}
}
}
Edit:
В дополнение к принятым и поддержанным ответам (спасибо), запуск его через 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();
}
Ответы
Ответ 1
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));
Здесь Func<char, bool>
, переданный в First()
, является выражением лямбда c => Bar().Contains(c)
. First()
будет вызывать вызов этого делегата для каждого символа в "Bar"
. "Тело" лямбда-выражения выполняется при каждом вызове, что приводит к тому, что Bar()
вызывается три раза.
Ответ 2
Вам нужно разбить его, чтобы узнать, почему:
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
вызывается только один раз и возвращает "__o". Затем делегат Func<char, bool>
, необходимый для First
, берется из этой строки, что в основном означает, что это Contains
в строке "__o", а не метод Foo
, поэтому "Foo" печатается только один раз.
В двух других случаях вы передаете выражение Lambda, которое затем вызывается для каждого символа, разделенное так же, как и выше, это будет просто:
Func<char, bool> func = c => Bar().Contains(c);
var bar = "Bar".First(func);
Здесь Bar
не вызывается для построения Func<char, bool>
, потому что он называется только внутри его тела, поэтому Bar
вызывается при каждом вызове этого Func<char, bool>
.
Ответ 3
Во всех трех случаях вы передаете функцию, которая берет символ и возвращает логическое значение. И во всех случаях функция будет выполняться для каждого символа в строке. Разница в том, как эти функции определены.
var foo = "Foo".First(Foo().Contains); // x 1
Здесь вы определяете эту функцию как функцию Contains
, принадлежащую объекту, возвращаемому Foo()
. Чтобы получить, что Foo()
нужно выполнить только один раз.
var bar = "Bar".First(c => Bar().Contains(c)); // x 3
var baz = "Baz".First(c => { return Baz().Contains(c); }); // x 3
В этих двух случаях вы определяете функцию, которая вызывает вызов Bar()
и Baz()
в вызове функции.