12

What's it called when a method that takes a lambda expression as the parameter, such as Enumerable.Where, is invoked without actually declaring a variable or method parameter in the expression?

For example, I'm familiar with this lambda expression syntax:

public string GetDigits(string input)
{
    return new String(input.Where(i => Char.IsDigit(i)).ToArray());
}

However, I was surprised to find out that this can also be written as:

public string GetDigits(string input)
{
    return new String(input.Where(Char.IsDigit).ToArray());
}

What's going on in that second snippet, where the Char.IsDigit() method is (apparently) being called with an implicit parameter? What is this syntax called?

6
  • ReSharper also suggests the simplified syntax, if applicable.
    – Uwe Keim
    Commented Jun 17, 2015 at 14:52
  • Than it's not a lambda-expression any longer, but passing a delegate as argument to another method.
    – Patrik
    Commented Jun 17, 2015 at 14:53
  • 1
    You're looking at a method group. Commented Jun 17, 2015 at 14:53
  • 2
    You might want to look at this question + answers: stackoverflow.com/questions/3841990
    – Dennis_E
    Commented Jun 17, 2015 at 14:53
  • 1
    As a side note, the ability to "drop the argument" is named η-conversion (pronounce eta-conversion) and roughly states that arg => fct (arg) is the same as the fct "objet" (not the application of the function) directly. That allow some possible compiler optimization when applicable (see @Dennis_E link for example)
    – Sehnsucht
    Commented Jun 17, 2015 at 15:01

4 Answers 4

17

Methods don't accept lambdas as parameters. They accept delegates as parameters. A lambda is just one way of creating a delegate.

Another way is supplying a method group, as is done in your second example, which can be converted to a delegate.

A similar way is to use the anonymous method feature. This was more or less replaced with lambdas when they were added though, so you don't see it much. Your example using that syntax would be:

Func<char, bool> predicate = delegate(char c) { return Char.IsDigit(c); };

Yet another way would be to create a delegate using Delegate.CreateDelegate. (This isn't something you see all that often though.)

A final way is to have a delegate variable that you got from somewhere else. (That somewhere else would have created the delegate using one of these other options.)

What's going on in that second snippet, where the Char.IsDigit() method is (apparently) being called with an implicit parameter? What is this syntax called?

It's not being called. That's the whole point. We're trying to create a delegate. A delegate is an object that keeps track of a method to be invoked, and an object that it should be invoked on. You can then invoke the delegate and it will call the method that was used to create it. So here you're not calling IsDigit, you're creating a delegate that is pointing to the IsDigit method, and that will call it whenever that delegate is invoked.

When you use a lambda you're creating a new method, possibly in a new class, (neither of which have a name you can refer to, but they'll have one at runtime) and the body of that anonymous method will call IsDigit. The lambda then resolves to a delegate pointing to that anonymous method, which maintains the semantics of the other example of having a method that when called, calls an anonymous method which, in its implementation, calls IsDigit. It's adding an extra layer of indirection (that may or may not just get optimized out at runtime) to accomplish the same thing.

3
  • When you use a lambda you're creating a new class Only if the lambda closes over an instance variable or field, otherwise it would be cached inside the invoking class. Commented Jun 17, 2015 at 15:13
  • Is it possible to use a non-static method as delegate which is not accessible directly? So if Char.IsDigit wasn't static, could you use it as delegate anyway? Commented Jun 17, 2015 at 15:19
  • 1
    @TimSchmelter You can use a method group of a non-static method to create a delegate, yes. The only requirement for a method group to be convertible to a delegate is for there to be a unique best overload of the method group that matches the signature of the delegate in question. If none of the overloads have the right signature, or there is ambiguity in which is "best", it'll error.
    – Servy
    Commented Jun 17, 2015 at 15:21
11

The signature of Enumerable.Where is:

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate
)

This:

input.Where(i => Char.IsDigit(i))

is equivalent to writing:

Func<char, bool> temp = i => Char.IsDigit(i);
input.Where(temp);

so it creates an anonymous function with a parameter i that calls Char.IsDigit.

This:

input.Where(Char.IsDigit)

is equivalent to

Func<char, bool> temp = Char.IsDigit;
input.Where(temp);

that is equivalent to:

Func<char, bool> temp = new Func<char, bool>(Char.IsDigit);
input.Where(temp);

so it creates a delegate to Char.IsDigit and then passes it to input.Where.

So the second one removes the "man-in-the-middle" (the anonymous function). In this particular case it is "legal" because the i parameter of the anonymous function is passed "as is" to Char.IsDigit. It would have been different if it was:

input.Where(i => !Char.IsDigit(i))

in this case, you can't remove the man-in-the-middle (the anonymous function).

There is no name for all of this (or you could call the first "creating and passing a delegate to an anonymous function" and the second "creating and passing a delegate created from a method group"... but they aren't beautiful catchphrases, they are more a description of what you are doing)

5
  • "There is no name for all of this." There is though: this is a method group. See also stackoverflow.com/questions/886822/what-is-a-method-group-in-c Commented Jun 17, 2015 at 14:58
  • @JeroenVannevel No, the method group is the Char.IsDigit in the input.Where(Char.IsDigit). Creating a delegate from a method group (that is what is done) doesn't have a name (or its name is exactly creating a delegate from a method group)
    – xanatos
    Commented Jun 17, 2015 at 14:59
  • @TimSchmelter Stolen :-)
    – xanatos
    Commented Jun 17, 2015 at 15:00
  • @xanatos There's no name for the action "using a method group when a delegate is expected", but you can use that (or something similar) to explain what's going on. You're right that there is no one word, or single term, that represents that concept, but you can at least say what that concept is reasonably succinctly.
    – Servy
    Commented Jun 17, 2015 at 15:03
  • 1
    @Servy Yep I've added some text to give a short description-that-isn't-a-catchphrase.
    – xanatos
    Commented Jun 17, 2015 at 15:05
8

Because the compiler will implicitly cast the method group to a delegate if it finds a single method that matches the expected signature, in this case a delegate taking a single char as input and returning a bool.

5

Your Where expects a Func<char, bool>, which is a delegate for methods that take a char argument and return a bool. Anything that matches this delegate is a valid argument for this Where.

  • The lambda you wrote initially matches this delegate by type inference: the compiler expects that i is char, based on the generic parameter of the enumerable source - and infers the return type as bool, because that's what the method call expression inside the lambda would return.
  • The Char.IsDigit method itself also matches this. Thus, referencing the method is another valid way of expressing the same thing. This is called a method group.

The semantic equivalence of these two possible arguments for Where also makes sense if you consider that for every lambda expression, the compiler generates an anonymous method and then passes that anonymous method where the delegate was expected.

To illustrate this, consider your original snippet:

Where(i => Char.IsDigit(i))

The above gets lowered by the compiler to:

bool AnAnonymousMethod(char i)
{
    return Char.IsDigit(i);
}

and then:

Where(AnAnonymousMethod)

As you can see, the lambda syntax (in cases where you don't have captured variables, like here) is just syntactic sugar for writing an anonymous method and then using the method group of this newly written method as the argument wherever a compatible delegate is expected.

Not the answer you're looking for? Browse other questions tagged or ask your own question.