Introduction
In this post, I’ll help you make better decisions on using C# anonymous methods. We all have our favorite or known ways to program certain solutions. But I believe that providing the right solution for the problem requires us to free our mind of all biases and think in the context of the problem. Every feature, including GOTO, has a place in this endless solution universe, therefore I’m not telling what not to use, I’m just showing what goes under the hood, so you can make your own decision based on your unique solution.
Background
There are situations where anonymous methods are really useful, especially if you have a small operation you want to do inline. Another point is that you lose on readability and testability: two important qualities of enterprise software. In smaller teams or developer owned projects, these qualities are sometimes omitted. (Nothing bad with that, as I said: there is a place for every feature). In bigger teams, especially if you have to review or continue another programmer’s code, it is really hard to read a line with complex inline code. I faced this problem many times on code review and code ownership changes.
So, let me show you what is going on under the hood, and you can make your own decision:
class Anonymous
{
public void Method()
{
bool test = true;
Action a = ()=> test = false;
}
}
Figure 1
In the example, I used the anonymous method on an Action to simplify the disassembled IL code. The code looks very straightforward and simple to write; there is only one delegate which captures the boolean variable test
and sets the value to false
. I kept the code simple to show the structural changes. There is even no call to the method; structurally, it does not have any effect. What happens to the structure in the background after you compile is shown in the disassembled MSIL code below (collapsed IL code by purpose; to point to the structure):
.class private auto ansi beforefieldinit Anonymous
extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
}
.method public hidebysig instance void Method() cil managed
{
}
.class auto ansi sealed nested private beforefieldinit <>c__DisplayClass1
extends [mscorlib]System.Object
{
.custom instance void [mscorlib]
System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
}
.method public hidebysig instance void b__0() cil managed
{
}
.field public bool test
}
}
Figure 2
As you can see, a private sealed
nested class named c_DisplayClass1
is created with a method named b__0
for our anonymous method. In the collapsed code, our original Method
method is modified in a way to create a new instance of c_DisplayClass1
to call the b__0
method. For those who are new to IL, I manually assembled it into the C# code below:
class ILAssembled
{
public void Method()
{
nested nt = new nested();
nt.test = true;
}
private sealed class nested
{
public bool test;
public void MethodNested()
{
test = false;
}
}
}
Figure 3
The compiled IL code of the C# code above is exactly the same (except for some not so important small changes). It is important to know that this IL result is for the code above. In fact, if I would move the local variable test
to an instance variable, a method would be created instead of a nested class. Therefore, it is always safer to take a look at the disassembled IL code. My favorite tool is RedGate’s .NET Reflector, which can disassemble into many languages. Another big advantage of looking into the post compiled code is to study .NET Framework namespaces and learn good practices.
Let’s go back to our code. It does not mean that you could write the code in Figure 3 instead of in Figure 1 and save some compile time :). The point is that the magic is in the compiler in exchange of testability, readability, and maybe some performance, because you end up with a nested class instead of a method.
Instead, if I would write the code below, I would gain all the qualities I was looking for in my unique solution:
public void Method()
{
bool test = true;
MyAction(test);
}
private void MyAction(bool test)
{
test = false;
}
Figure 4
As you can see, I created a method named MyAction
to implement the functionality. Now I can create a unit test to test MyAction
, and my colleagues can also understand the code at first look. It is really important to understand the code at first look if you are reviewing tens of classes; encrypting variable and method names, over using implicit type var
, and over using anonymous methods double or triple the review time.
For clarity, I also provided below the disassembled IL code structure of Figure 4 (xollapsed IL code by purpose; to point to the structure):
.class private auto ansi beforefieldinit NonAnonymous
extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
}
.method public hidebysig instance void Method() cil managed
{
}
.method private hidebysig instance void MyAction(bool test) cil managed
{
}
}
Figure 5
As you can see, there is no nested class declared to run the anonymous method.
Conclusion
I believe knowing what you are using is the key to making the right decision for your unique solution.
CodeProject