I noticed something strange when trying to pass a StringBuilder
's Append
method to a function that took an Action<string>
.
public void DoStuff(Action<string> handler)
{
// Do stuff, call handler("data");
}
For testing purposes, I just want to write the data into a StringBuilder
, so I tried to call it like this:
var output = new StringBuilder();
DoStuff(output.Append);
However, this gives a compile error, because the Append
method does not match the required signature (it returns a reference back to the StringBuilder
, not void as my method wants):
'System.Text.StringBuilder System.Text.StringBuilder.Append(string)' has the wrong return type
Without thinking, I changed the code to this:
var output = new StringBuilder();
DoStuff(s => output.Append(s));
This compiled fine.
Then I got confused; realising that s => output.Append(s)
should also return the StringBuilder
, aren't they the same?
So, why does this work? Why can s => output.Append(s)
have the return value discarded silently, yet output.Append
cannot?
s => output.Append(s)
creates a new lambda expression, which is inferred (from the context) to have a return type of void
.
Therefore, the value of the expression body is ignored.
This is compiled to a separate method that calls Append()
and returns void (this exactly matching the delegate)
In contrast, when you attempt to convert a method group to a delegate, the conversion must match exactly.
The spec (§6.5) says:
Specifically, an anonymous function F is compatible with a delegate type D provided:
- If the body of F is an expression, and either D has a void return type or F is async and D has the return type Task, then when each parameter of F is given the type of the corresponding parameter in D, the body of F is a valid expression (wrt §7) that would be permitted as a statement-expression (§8.6).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With