When comparing anonymous methods to lambda expressions, I've seen explanations that anonymous methods provide flexibility.
Flexibility here means that you can omit parameters from anonymous methods.
// inflexible anonymous method
Action<int> action = delegate(int number)
{
Console.WriteLine($"Anonymous method: {number}");
}
action(1234);
// Output
// Anonymous method: 1234
// flexible anonymous method
Action<int> action = delegate
{
Console.WriteLine("Anonymous method");
}
action(1234);
// Output
// Anonymous method
Action<int> action = (number) =>
{
Console.WriteLine($"Lambda expression: {number}");
};
action(1234);
// Output
// Lambda expression: 1234
However, I have two questions.
First, how does an anonymous method with an omitted parameter use the passed value? (1234 here)
Second, where do you use the flexibility that anonymous methods provide?
CodePudding user response:
I'll only answer the first question. The second question is highly opinion based, depends on context and at best I can offer incomplete / insufficient examples. Refer to the examples offered in Action Delegate. One use case is Composability
How does an anonymous method with an omitted parameter use the passed value?
It doesn't. The generated IL (intermediate language) doesn't account in any way for the provided argument. Which seems logical because if it does, it needs to have knowledge about all the callers of that method.
IL_0000: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0005: brtrue.s IL_0018
IL_0007: ldnull
IL_0008: ldftn UserQuery.<Main>b__0
IL_000E: newobj System.Action<System.Int32>..ctor
IL_0013: stsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0018: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_001D: stloc.0 // action
IL_001E: ldloc.0 // action
IL_001F: ldc.i4 D2 04 00 00
IL_0024: callvirt System.Action<System.Int32>.Invoke
IL_0029: ret
<Main>b__0:
IL_0000: ldstr "Anonymous method"
IL_0005: call System.Console.WriteLine
IL_000A: ret
On IL_001F the value 1234 is pushed on the stack. In <Main>b__0 there is no "pop" from the stack. So the value is lost.
If you would have a method that takes an int
, like so void test(int arg)
the IL would look like:
test:
IL_0000: ldstr "Test method"
IL_0005: ldarg.1
IL_0006: box System.Int32
IL_000B: call System.Console.WriteLine
IL_0010: ret
Here it does "pop" the argument of the stack in IL_005.
It is worth remembering that a delegate is a generated type that derives from System.Delegate and there is an implementation detail you see on line IL_0024.
The common language runtime provides an Invoke method for each delegate type, with the same signature as the delegate. You do not have to call this method explicitly from C#, Visual Basic, or Visual C , because the compilers call it automatically.
Do note that your first and last example return the same IL for the .Net 6 compiler, which you can inspect here