Подтвердить что ты не робот

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();
}
4b9b3361

Ответ 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() в вызове функции.