Search code examples
c#linqlambdaanonymousenumerable

c#: Isn't Where(), OrderBy() and Select() from Enumerable Class supposed to take a delegate type, lambda expression or anonymous type as a parameter


I have got a question. See the two blocks of code. Isn't Where(), OrderBy() and Select() from IEnumerable Class supposed to take a delegate type, lambda expression or anonymous type as a parameter. If so, how did the QueryOverStringWithRawDelegate() produce the same results as QueryOverStringsWithExtensionMethods()?

void QueryOverStringsWithExtensionMethods()
{
    // Assume we have an array of strings
    string[] currentVideoGames = { "Morrowind", "Uncharted 2", "Fallout 3", "Daxter", "Bio Shock 4" };

    IEnumerable<string> subset = currentVideoGames.Where(game => game.Contains(" ")).OrderBy(game => game).Select(delegate (string game) { return game; });

    Console.WriteLine("Query Over Strings With Extension Method");
    foreach (var s in subset)
    {
        Console.WriteLine("Items: {0}", s);
    }
}

and

void QueryStringsWithRawDelegates()
{
    // Assume we have an array of strings
    string[] currentVideoGames = { "Morrowind", "Uncharted 2", "Fallout 3", "Daxter", "Bio Shock 4" };


    var subset = currentVideoGames.Where(Filter).OrderBy(ProcessItems).Select(ProcessItems);

    foreach (var s in subset)
    {
        Console.WriteLine("Items: {0}", s);
    }

    string ProcessItems(string game)
    {
        return game;
    }
    bool Filter(string game)
    {
        return game.Contains(" ");
    }
}

Thank you for your help !


Solution

  • currentVideoGames.Where(Filter)
    

    is simply shorthand for:

    currentVideoGames.Where(new Func<string, bool>(Filter))
    

    That is, the compiler sees that you've got a method which takes a delegate type Func<string, bool>, it sees that you're giving it a method which has the signature bool Filter(string) (strictly, a method group of one or more overloads, one of which has a signature that's close enough), and it automatically inserts the code to instantiate a new delegate instance.

    This same language feature lets you write things like:

    SomeEvent += Handler;
    

    rather than:

    SomeEvent += new EventHandler(Handler);
    

    See this on SharpLab.


    Similarly:

    currentVideoGames.Where(game => game.Contains(" "))
    

    is shorthand for:

    currentVideoGames.Where(new Func<string, bool>(CompilerGeneratedFunction))
    

    where CompilerGeneratedFunction will look something like:

    bool CompilerGeneratedFunction(string x)
    {
        return x.Contains(" ");
    }
    

    See this on SharpLab. It so happens that the compiler's put CompilerGeneratedFunction (which it called <M>b__0_0) in a new inner class, and it caches the Func<string, bool> that it instantiates for performance reasons.