Fun with ILDASM: Extension vs Static Methods
I recently had a comment on a very old blog post that led to a conversation between the commenter and me. During the conversation, I made the comment that extension methods in C# are just syntactic sugar over plain ol’ static methods. He asked me to do a post exploring this in further detail, which is what I’m doing today. However, there isn’t a ton more to explain in this regard anymore than explaining that (2 + 2) and 4 are representations of the same concept. So, I thought I’d “show, don’t tell” and, in doing so, introduce you to the useful concept of using a disassembler to allow you to examine .NET’s IL byte code.
I won’t go into a ton of detail about virtual machines and just in time compilation or anything, but I will describe just enough for the purpose of this post. When you write C# code and perform a build, the compiler turns this into what’s called “IL” (intermediate language). Java works in the same way, and its intermediate product is generally referred to as byte code. When an IL executable is executed, the .NET framework is running, and this intermediate code is compiled, on the fly, into machine code. So what you have with both .NET in Java is a 2 stage compilation process: source code to intermediate code, and intermediate code to machine code.
Where things get interesting for our purposes is that the disassembled intermediate code is legible and you can work with it, reading it or even writing it directly (which is how AOP IL-Weaving tools like Postsharp work their magic). What gets even more interesting is that all of the .NET languages (C#, VB, F#, etc) compile to the same IL code and are treated the same by the framework when compiled into machine code. This is what is meant by languages “targeting the .NET framework” — there is a compiler that resolves these languages into .NET IL. If you’ve ever wondered why it’s possible to have things like “Iron Python” that can exist in the .NET ecosystem, this is why. Someone has written code that will parse Python source code and generate .NET IL (you’ll also see this idea referred to as “Common Language Infrastructure” or CLI).
Anyway, what better way to look at the differences or lack thereof between static methods and extension methods. Let’s write them and see what the IL looks like! But, in order to do that, we need to do a little prep work first. We’re going to need easy access to a tool that can read .NET exe and dll files and produce the assembly in a readable, text-file form. So, here’s what we’re going to do.
- In Visual Studio, go to Tools->External Tools.
- Click “Add” and you will be prompted to fill out the text boxes below.
- Fill them out as shown here (you may have to search for ILDASM.exe, but it should be in Microsoft SDKs under Program Files (x86):
- Click “Apply.” ILDASM will now appear as a menu option in the Tools menu.
Now, let’s get to work. I’m going to create a new project that’s as simple as can be. It’s a class library with one class called “Adder.” Here’s the code:
public static class Adder
{
public static int AddTwoNumbers(int first, int second)
{
return first + second;
}
}
Let no one accuse me of code bloat! That’s it. That’s the only class/method in the solution. So, let’s run ILDASM on it and see what happens. To do that, select “ILDASM” from the Tools menu, and it will launch a window with nothing in it. Go to “File->Open” (or Ctrl-O) and it will launch you in your project’s output directory. (This is why I had you add “$(TargetDir)” in the external tools window. Click the DLL, and you’ll be treated to a hierarchical makeup of your assembly, as shown here:
So, let’s see what the method looks like in IL Code (just double click it):
.method public hidebysig static int32 AddTwoNumbers(int32 first, int32 second) cil managed { // Code size 9 (0x9) .maxstack 2 .locals init ([0] int32 CS$1$0000) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldarg.1 IL_0003: add IL_0004: stloc.0 IL_0005: br.s IL_0007 IL_0007: ldloc.0 IL_0008: ret } // end of method Adder::AddTwoNumbers
Alright… thinking back to my days using assembly language, this looks vaguely familiar. Load the arguments into registers or something, add them, you get the gist.
So, let’s see what happens when we change the source code to use an extension method. Here’s the new code:
public static class Adder
{
public static int AddTwoNumbers(this int first, int second)
{
return first + second;
}
}
Note, the only difference is the addition of “this” before “int first.” That is what turns this into an extension method and alters the calling semantics (though you can still call extension methods the same way you would normal static methods).
So, let’s see what the IL code looks like for that:
.method public hidebysig static int32 AddTwoNumbers(int32 first, int32 second) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) // Code size 9 (0x9) .maxstack 2 .locals init ([0] int32 CS$1$0000) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldarg.1 IL_0003: add IL_0004: stloc.0 IL_0005: br.s IL_0007 IL_0007: ldloc.0 IL_0008: ret } // end of method Adder::AddTwoNumbers
The only difference between this and the plain static version is the presence of the line:
.custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
The “this” keyword results in the generation of this attribute, and its purpose is to allow the compiler to flag it as an extension method. (For more on this, see this old post from Scott Hanselman: How do Extension Methods work and why was a new CLR not required?). The actual substance of the method is completely identical.
So, there you have it. As far as the compiler is concerned, the difference between static and extension methods is “extension methods are static methods with an extension method attribute.” Now, I could go into my opinion on which should be used, when, and how, but it would be just that: my opinion. And your mileage and opinion may vary. The fact of the matter is that, from the compiler’s perspective, they’re the same, so when and how you use one versus the other is really just a matter of your team’s preferences, comfort levels, and ideas about readability.