Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

linq

I'm lost on what keywords to google for... Could anyone please point me to an MSDN page or SO answer explaining why Foo() is only called once? Especially since First only has a single overload with a predicate. What optimisation is going on here?

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:

In addition to accepted and upvoted answers (thanks), running it through ILSpy helped visually clarify the order for me as well.

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();
}
like image 306
Ilya Kozhevnikov Avatar asked Jan 27 '15 15:01

Ilya Kozhevnikov


3 Answers

Foo() is only called once because the expression you pass to First() is Foo().Contains.

To evaluate this expression, Foo() only has to be called once.

Let's consider the differences between the first and second snippets:

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

Here, First() expects a Func<char, bool> argument. Foo() is called (once) and a member access for Contains is performed on the result. The result of that member access is indeed a Func<char, bool>, so the code is valid and that delegate is passed to First(), which proceeds to invoke it for every character in "Foo". Note that we're done with calling Foo() here, since invoking the delegate does not mean we have to evaluate Foo() again.

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

Here, the Func<char, bool> passed to First() is the lambda expression c => Bar().Contains(c). First() will proceed to invoke that delegate for every character in "Bar". The "body" of the lambda expression is executed on every invocation, which results in Bar() being called three times.

like image 198
Frédéric Hamidi Avatar answered Nov 11 '22 05:11

Frédéric Hamidi


You need to split it to directly see why:

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

is basically:

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

As you see now, Foo is only called once and returns "__o". Then the Func<char, bool> delegate needed by First is taken from that string, which basically means it is a Contains on the string "__o" not the method Foo, hence "Foo" is only printed once.

In the other two cases you pass in a Lambda-expression which is then called for every character - splitted the same way as above this would be just:

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

Here Bar is not called to construct the Func<char, bool>, because it is only called "inside" its body, which is why Bar is then called on every call of that Func<char, bool>.

like image 22
Christoph Fink Avatar answered Nov 11 '22 07:11

Christoph Fink


In all three cases you are passing a function that takes a character and returns a boolean. And in all cases the function will be executed for each character in the string. The difference is how those functions are defined.

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

Here you're defining that function as the Contains function belonging to the object returned by Foo(). To get that Foo() needs to be executed just once.

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

In these two cases you're defining a function that ends up calling Bar() and Baz() in the function call.

like image 4
Filip Stankovski Avatar answered Nov 11 '22 05:11

Filip Stankovski