Friday, June 24, 2011

Macros in C#

[beginner level, originally posted on an internal company blog on Feb 1st]

Hi,


Today I want to talk about C# macros. Those are not 
macros in the
sense of C's #define statements but instead a syntactic sugar which
enables us to write simple, more readable and shorter code. My point
is that all those 
macros can be avoided by writing other code which
will result in the exact same IL. You might have not noticed these macros before or took them for C# keywords but in this article I will use Reflector (edit: Reflector was still free back then) to see what the compiler does in the case of four commonly used statements.

Extension Method

An extension method is a method defined on an existing class which can
be called using the dot operator. For example I can define the
following extension method on the string class:


public static class StringExtensions
{
  public static void MyStringExtensionMethod(this string input, int number)
  {
    Console.Out.WriteLine(String.Format(input, number));
  }
}


Then I call it in my code
string s = "My number is {0}";
s.MyStringExtensionMethod(10);

And the output is: My number is 10

Well, we don't expect the compiler to edit the existing string class
and add a method to it nor do we think a new class is created which
inherits the string class (it is sealed!).
So what did the compiler do? A hint to that is the definition of an
extension method. It is static in a static class. In fact the only
thing that makes this method an extension is the fact we added the
"this" keyword in the definition. The simple answer is that the
compiler did nothing. The only thing happened is that the "this"
keyword enabled the dot syntax but the call remained the same. In this
case the actual call to the method is
MyStringExtensionMethod(s,10);

Let's compare the following code with Reflector
static void Main(string[] args)
{
   string s = "My number is {0}";
   s.MyStringExtensionMethod(10);
   StringExtensions.MyStringExtensionMethod(s, 10);
}

The disassembly is somewhat strange:

private static void Main(string[] args)
{
   string s = "My number is {0}";
   s.MyStringExtensionMethod(10);
   s.MyStringExtensionMethod(10);
}

But we see that the IL code is exactly the same and that this is only
the interpretation of the tool

   L_0007: ldloc.0
   L_0008: ldc.i4.s 10
   L_000a: call void
Macros.StringExtensions::MyStringExtensionMethod(string, int32)
   L_000f: nop
   L_0010: ldloc.0
   L_0011: ldc.i4.s 10
   L_0013: call void
Macros.StringExtensions::MyStringExtensionMethod(string, int32)
   L_0018: nop
"using" statement 

The "using" statement is used to create, work with, and dispose an
IDisposable object. Probably the most common case to meet this
statement is when working with streams.
Lets look at a typical piece of code that uses the "using" statement.

using (StreamReader reader = new StreamReader("MyFile.txt"))
{
    Console.Out.WriteLine(reader.ReadToEnd());
}

In this code we print the content on a text file onto the console. We
don't want to lock the file after we finish reading so we free it.
This is exactly what the using statement does for us. We could simply
write the following code and not use the using statement:

    StreamReader myReader = new StreamReader("MyFile.txt");
    try
    {
      Console.Out.WriteLine(myReader.ReadToEnd());
    }
    finally
    {
      myReader.Dispose();
    }
Lets view the result in Reflector:

    using (StreamReader reader = new StreamReader("MyFile.txt"))
    {
      Console.Out.WriteLine(reader.ReadToEnd());
    }
    using (StreamReader myReader = new StreamReader("MyFile.txt"))
    {
      Console.Out.WriteLine(myReader.ReadToEnd());
    }


Again, the tool was smarter and formatted the code to the same statement.


"event" statement

Yes, you might not have noticed this but the "event" keyword is actually a
macro. I will not try to recreate the code because, as you will soon
see, it is quite complex but it is nice to have a look at it.

We will include this simple statement in our code and see the resulting IL code:

public event EventHandler<EventArgs> MyEvent;

This is a shorthand notation for an event with the signature void
Func(object sender, EventArgs args)
Now lets look at the resulting C# code:

  // Fields
   private EventHandler<EventArgs> MyEvent;
 
   // Events
   public event EventHandler<EventArgs> MyEvent;

First of all the compiler added an additional member of type
EventHandler<EventArgs> with the same name as the event (this is the
member you are actually using).
If we check further we can see that this is simply a MulticastDelegate
(the IL of its definition is):
.class public auto ansi serializable sealed
EventHandler<(System.EventArgs) TEventArgs> extends
System.MulticastDelegate


 So we are actually working with a MulticastDelegate but we also get
two custom method for adding and removing event handlers to it:
public void add_MyEvent(EventHandler<EventArgs> value)
{
   EventHandler<EventArgs> handler2;
   EventHandler<EventArgs> myEvent = this.MyEvent;
   do
   {
       handler2 = myEvent;
       EventHandler<EventArgs> handler3 = (EventHandler<EventArgs>)
Delegate.Combine(handler2, value);
       myEvent =
Interlocked.CompareExchange<EventHandler<EventArgs>>(ref this.MyEvent,
handler3, handler2);
   }
   while (myEvent != handler2);
}

and

public void remove_MyEvent(EventHandler<EventArgs> value)
{
   EventHandler<EventArgs> handler2;
   EventHandler<EventArgs> myEvent = this.MyEvent;
   do
   {
       handler2 = myEvent;
       EventHandler<EventArgs> handler3 = (EventHandler<EventArgs>)
Delegate.Remove(handler2, value);
       myEvent =
Interlocked.CompareExchange<EventHandler<EventArgs>>(ref this.MyEvent,
handler3, handler2);
   }
   while (myEvent != handler2);
}

And this is the code that get executed when you call "+= " or "-=" on
your event member.

[edit: The next section was not in the original post]
"lock" statement

Perhaps the most well known macro in C# is the lock statement. The lock statement is used to create a critical section in your code which relies on some object which is used as a lock on that section of code. The "lock" keyword is just a macro for a try-finally block which uses the Monitor class to create a critical section.

For example the following code:

      object myLock = new object();
      lock (myLock)
      {
        Console.WriteLine("Inside MyLock");
      }
 
      Monitor.Enter(myLock);
      try
      {
        Console.WriteLine("Inside MyLock");
      }
      finally
      {
        Monitor.Exit(myLock);
      }
 
When compiled and decompiled using Telerik justDecompile will result in the following code:

 object myLock = new object();
 Monitor.Enter(object obj = myLock);
 try
 {
  Console.WriteLine("Inside MyLock");
 }
 finally
 {
  Monitor.Exit(obj);
 }
 Monitor.Enter(myLock);
 try
 {
  Console.WriteLine("Inside MyLock");
 }
 finally
 {
  Monitor.Exit(myLock);
 }

We can clearly see that the two statements are identical (except this somewhat strange syntax generated by Mono.Cecil in the first Monitor.Enter call.


I hope you enjoyed this post.
Thank you for reading,
Boris

No comments:

Post a Comment