Home
Contact Us
About Us
Site Map
My Family
CSharp
Compiler Sugar
For Sale

C# Compiler Sugar

by Faisal Baqai

 

(Note: Since C# 3.0 is all compiler sugar so I'm not covering C# 3.0 features here.)

Copy Rights: I allow you to copy anything from this page but if you are putting it on the web then please mention it and link back to my page.

 

Many of us using compilers for a while but we never pay attention to all compiler sugar. I thought its a good idea to put all known compiler sugar together. I'm using 'ildasm' to view IL and in some cases I'm using 'Reflector' to view generated code in C# format instead of IL.

 

System.Object (class)

When you create a new class, by default it is derived from System.Object.

class CompilerSugar

{

}

IL:

.class private auto ansi beforefieldinit CompilerSugar

extends [mscorlib]System.Object

{

} // end of class CompilerSugar

 

System.ValueType (struct)

'struct' is consider as ValueType but the way it is implemented by compiler is just another class which is derived from 'System.ValueType' and marks it a sealed class.

struct CompilerSugarValueType

{

}

.class private sequential ansi sealed beforefieldinit CompilerSugarValueType

extends [mscorlib]System.ValueType

{

.pack 0

.size 1

} // end of class CompilerSugarValueType

 

System.Enum (enum):

C# defines 'enum' as a sealed class which is derived from 'System.Enum', which is derived from 'System.ValueType' and few interfaces (IComparable, IFormattable, IConvertible). If you don't define integer value for enum value then compiler assigns it values (32-bit) starting from 0. All enum values are declared as fields which are public static ValueTypes

enum EnumSugar

{

RED,

BLUE,

GREEN

}

 

IL:

.class private auto ansi sealed EnumSugar

extends [mscorlib]System.Enum

{

} // end of class EnumSugar

 

.field public static literal valuetype EnumSugar BLUE = int32(0x00000001)

.field public static literal valuetype EnumSugar GREEN = int32(0x00000002)

.field public static literal valuetype EnumSugar RED = int32(0x00000000)

 

Interface and Abstract Class

You might have notice this question in interviews (whats the difference between 'Interface' and 'Abstract class') and I'm pretty sure that many of those interviews even don't know that C# creates an abstract class for interface. Only difference is that when it declares interface class then it doesn't derive it from 'System.Object'.

 

interface InterfaceSugar

{

int Bar();

}

abstract class AbstractSugar

{

public abstract int Bar();

}

.class interface private abstract auto ansi InterfaceSugar

{

} // end of class InterfaceSugar

 

 

.class private abstract auto ansi beforefieldinit AbstractSugar

extends [mscorlib]System.Object

{

} // end of class AbstractSugar

 

While there is absolutely no difference between abstract function and method defined in Interface. Both are declared as abstract virtual.

 

.method public hidebysig newslot abstract virtual

instance int32 Bar() cil managed

{

} // end of method InterfaceSugar::Bar

 

.method public hidebysig newslot abstract virtual

instance int32 Bar() cil managed

{

} // end of method AbstractSugar::Bar

 

Virtual vs Abstract Function:

In terms of signature, there is not much difference between abstract and virtual functions. Call for both are decided at runtime using VTable. Major difference is that for abstract function, compiler doesn't generate any body while for virtual function there can be body of the method. From compiler rules points of view there are few differences but that has nothing to do with compiler sugar so not going in details.

 

abstract class AbstractSugar

{

public abstract void ABS();

public virtual void VIRT() { }

}

.method public hidebysig newslot abstract virtual

instance void ABS() cil managed

{

} // end of method AbstractSugar::ABS

 

 

.method public hidebysig newslot virtual

instance void VIRT() cil managed

{

// Code size 2 (0x2)

.maxstack 8

IL_0000: nop

IL_0001: ret

} // end of method AbstractSugar::VIRT

 

 

Aliases:

C# defines bunch of aliases to make it more like C/C++. For example when you write 'int' it get converted into'System.Int32' or when you write 'long' it gets converted into 'System.Int64'. Seems close enough but what about 'float'? Interestingly 'float' alias for 'System.Single'

 

int = System.Int32

long = System.Int64

decimal = System.Decimal

float = System.Single

string = System.String

byte = System.Byte

char = System.Char

 

 

Nullable was introduced in C# 2.0 and you can just add '?' in front of any 'ValueType' data type (such as int, float, long etc or your own struct).

int? i;

long? l;

float? f;

decimal? d;

C# 2.0 has an other special type 'System.Nullable<T>' and above example will be solved as:

System.Nullable<System.Int32> i;

System.Nullable<System.Int64> l;

System.Nullable<System.Single> f;

System.Nullable<System.Decimal> d;

 

Default Constructor (ctor)

When no constructor is defined then compiler creates a Default constructor for you.

 

class CompilerSugar

{

}

 

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// Code size 7 (0x7)

.maxstack 8

IL_0000: ldarg.0

IL_0001: call instance void [mscorlib]System.Object::.ctor()

IL_0006: ret

} // end of method CompilerSugar::.ctor

 

Static Constructor (cctor):

Static Constructors are called only once per class. A common question is when they are called. Static Constructor is called at the firt call of that class (either thru instance method or thru static method). Another interesting thing is that if you have a static field which has a initializer as well and you don't have static constructor defined then compiler generates static constructor and initialize static variable code there. Also there is no call to base constructor in static constructor.

 

public class CompilerSugar

{

static string g = "123";

}

.method private hidebysig specialname rtspecialname static

void .cctor() cil managed

{

// Code size 11 (0xb)

.maxstack 8

IL_0000: ldstr "123"

IL_0005: stsfld string CompilerSugar::g

IL_000a: ret

} // end of method CompilerSugar::.cctor

 

 

 

 

 

Initialize Field Variables:

C++ doesn't allow initialization of field variable. In C++ you have to initialize them in constructor. C# allows you to initialize field variables at time of declaration but thats just sugar. Actually it copies that code before first line of your constructors code (in all constructors). So lets say if you have 10 overloaded ctors then it will copy that field init code in all 10 ctors. Which is fine unless if your ctor was specifically assigning value to that field then its redundant code

 

class CompilerSugar

{

int _member = 10;

public CompilerSugar()

{

}

public CompilerSugar(int i)

{

_member = i;

}

}

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// Code size 18 (0x12)

.maxstack 8

IL_0000: ldarg.0

IL_0001: ldc.i4.s 10

IL_0003: stfld int32 CompilerSugar::_member

IL_0008: ldarg.0

IL_0009: call instance void [mscorlib]System.Object::.ctor()

IL_000e: nop

IL_000f: nop

IL_0010: nop

IL_0011: ret

} // end of method CompilerSugar::.ctor

 

 

.method public hidebysig specialname rtspecialname

instance void .ctor(int32 i) cil managed

{

// Code size 25 (0x19)

.maxstack 8

IL_0000: ldarg.0

IL_0001: ldc.i4.s 10

IL_0003: stfld int32 CompilerSugar::_member

IL_0008: ldarg.0

IL_0009: call instance void [mscorlib]System.Object::.ctor()

IL_000e: nop

IL_000f: nop

IL_0010: ldarg.0

IL_0011: ldarg.1

IL_0012: stfld int32 CompilerSugar::_member

IL_0017: nop

IL_0018: ret

} // end of method CompilerSugar::.ctor

 

 

 

Base Ctor calls:

C# compiler adds call to 'System.Object' ctor before any code in actual ctor code:

 

class CompilerSugar

{

public CompilerSugar()

{

Console.WriteLine("init");

}

}

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// Code size 21 (0x15)

.maxstack 8

IL_0000: ldarg.0

IL_0001: call instance void [mscorlib]System.Object::.ctor()

IL_0006: nop

IL_0007: nop

IL_0008: ldstr "init"

IL_000d: call void [mscorlib]System.Console::WriteLine(string)

IL_0012: nop

IL_0013: nop

IL_0014: ret

} // end of method CompilerSugar::.ctor

 

 

Properties:

There was no concept of Properties in C++, typically you would be calling "int GetLength(void)" and "void SetLength (int)" functions. C# provides properties to achive same things but again its just a compiler sugar. If you look at generated code, C# actually generates functions named: "int get_Length()" and "void set_Length.(int)"

 

public int Length

{

set { }

get { return 0; }

}

.method public hidebysig specialname instance int32

get_Length() cil managed

.method public hidebysig specialname instance void

set_Length(int32 'value') cil managed

 

 

New feature in C# 2.0 where you even don't need to declare field variable or body of Property. Compiler itself creates a field variable add standard code (variable = value for 'set' and return value for 'get'). Generated field variable is unique and in special format.

 

class CompilerSugar

{

public int Length { get; set; }

}

.field private int32 '<Length>k__BackingField'

.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

 

.method public hidebysig specialname instance int32

get_Length() cil managed

{

.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

// Code size 11 (0xb)

.maxstack 1

.locals init (int32 V_0)

IL_0000: ldarg.0

IL_0001: ldfld int32 CompilerSugar::'<Length>k__BackingField'

IL_0006: stloc.0

IL_0007: br.s IL_0009

IL_0009: ldloc.0

IL_000a: ret

} // end of method CompilerSugar::get_Length

 

.method public hidebysig specialname instance void

set_Length(int32 'value') cil managed

{

.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

// Code size 8 (0x8)

.maxstack 8

IL_0000: ldarg.0

IL_0001: ldarg.1

IL_0002: stfld int32 CompilerSugar::'<Length>k__BackingField'

IL_0007: ret

} // end of method CompilerSugar::set_Length

 

 

 

Indexer:

Indexer is similar concept as Properties. In C/C++ you would be calling "int GetAt(int)" and "void SetAt(int, int)". C# provides indexer which are easy to read but generated code looks pretty much same as we used to do in C/C++. i.e. 'get_Item(int)' and 'set_Item(int, int)'.

 

 

public class CompilerSugar

{

public int this[int i]

{

get { return 0; }

set { }

}

}

 

 

.method public hidebysig specialname instance int32

get_Item(int32 i) cil manag

 

.method public hidebysig specialname instance void

set_Item(int32 i, int32 'value') cil managed

 

Attributes:

Attributes are special class in C# where if you derive your class from 'Attribute' class and if your class name ends with 'Attribute' string then compiler allows you to ignore 'Attribute' string. But when compiler generates IL, it will have complete name.

 

class CompilerSugar

{

[Custom] //<== Note, Actual class name was 'CustomAttribute'

public void Foo()

{

}

}

class CustomAttribute : Attribute

{

}

 

class Custom

{

}

 

IL

.method public hidebysig instance void Foo() cil managed

{

.custom instance void CustomAttribute::.ctor() = ( 01 00 00 00 )

 

Optimization for unreachable code:

public void Foo()

{

return;

int i = 0;

if (i > 0)

Console.WriteLine();

}

 

IL:

.method public hidebysig instance void Foo() cil managed

{

// Code size 4 (0x4)

.maxstack 0

.locals init ([0] int32 i)

IL_0000: nop

IL_0001: br.s IL_0003

IL_0003: ret

} // end of method CompilerSugar::Foo

 

Finalize/Destructor

Destructors were very important in C/C++ to free memory/resources in your class. C# also provide same concept with same syntax while internally they are called Finalize. There are few interesting things to note. Same as Constructor, it calls 'Object.Finalize' and to gurantee that 'Object.Finalize' must be called, it encapsulate all user code into 'try/finally' block.

 

class CompilerSugar

{

~CompilerSugar()

{

}

}

 

IL:

.method family hidebysig virtual instance void

Finalize() cil managed

{

// Code size 14 (0xe)

.maxstack 1

.try

{

IL_0000: nop

IL_0001: nop

IL_0002: leave.s IL_000c

} // end .try

finally

{

IL_0004: ldarg.0

IL_0005: call instance void [mscorlib]System.Object::Finalize()

IL_000a: nop

IL_000b: endfinally

} // end handler

IL_000c: nop

IL_000d: ret

} // end of method CompilerSugar::Finalize

 

If you declare 'Finalize' in your class (and no destructor) then you get warning from compiler:

 

public class CompilerSugar

{

void Finalize()

{

}

}

warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?

 

using block:

Using is a new interesting concept in C#. Main requirement for 'using' block is that object must derive from 'IDisposable' and compiler will gurantee that object's disposed will be called at the end of using block. To To achieve this, compiler encapsulate usercode into try/finally and in finally it calls Dispose function.

 

class CompilerSugar

{

public void Foo()

{

using (StreamReader sw = new StreamReader("c:\\foo.txt"))

{

}

}

}

.method public hidebysig instance void Foo() cil managed

{

// Code size 34 (0x22)

.maxstack 2

.locals init ([0] class [mscorlib]System.IO.StreamReader sw,

[1] bool CS$4$0000)

IL_0000: nop

IL_0001: ldstr "c:\\foo.txt"

IL_0006: newobj instance void [mscorlib]System.IO.StreamReader::.ctor(string)

IL_000b: stloc.0

.try

{

IL_000c: nop

IL_000d: nop

IL_000e: leave.s IL_0020

} // end .try

finally

{

IL_0010: ldloc.0

IL_0011: ldnull

IL_0012: ceq

IL_0014: stloc.1

IL_0015: ldloc.1

IL_0016: brtrue.s IL_001f

IL_0018: ldloc.0

IL_0019: callvirt instance void [mscorlib]System.IDisposable::Dispose()

IL_001e: nop

IL_001f: endfinally

} // end handler

IL_0020: nop

IL_0021: ret

} // end of method CompilerSugar::Foo

 

Lock block (Threading):

C# provides locking mechanism to avoid threading issues using 'lock' block. Where you specify resource to lock and the code which will be in that block. What compiler does it it calls "System.Threading.Monitor.Enter" and then encapsulate user code into try block and then calls "System.Threading.Monitor.Exit" in finally block.

 

public class CompilerSugar

{

static string g = "123";

public CompilerSugar()

{

lock (g)

{

Console.WriteLine(g[0]);

}

}

}

 

IL:

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// Code size 53 (0x35)

.maxstack 2

.locals init ([0] string CS$2$0000)

IL_0000: ldarg.0

IL_0001: call instance void [mscorlib]System.Object::.ctor()

IL_0006: nop

IL_0007: nop

IL_0008: ldsfld string CompilerSugar::g

IL_000d: dup

IL_000e: stloc.0

IL_000f: call void [mscorlib]System.Threading.Monitor::Enter(object)

IL_0014: nop

.try

{

IL_0015: nop

IL_0016: ldsfld string CompilerSugar::g

IL_001b: ldc.i4.0

IL_001c: callvirt instance char [mscorlib]System.String::get_Chars(int32)

IL_0021: call void [mscorlib]System.Console::WriteLine(char)

IL_0026: nop

IL_0027: nop

IL_0028: leave.s IL_0032

} // end .try

finally

{

IL_002a: ldloc.0

IL_002b: call void [mscorlib]System.Threading.Monitor::Exit(object)

IL_0030: nop

IL_0031: endfinally

} // end handler

IL_0032: nop

IL_0033: nop

IL_0034: ret

} // end of method CompilerSugar::.ctor

 

 

 

Operator Overloading:

I think now all Object Oriented languages supports Operator Overloading. This feature provide more readibility in your code when you need to use custom operations with standard operators (e.g. +, -, ==, etc). In old days when operator loverloading wasn't supported developers used to write functions like 'Add', 'Substract', 'Equals', etc. Modern compilers does almost same thing including C#.

Following is example of '==' and '!=' operators overloading where compiler generates 'op_Equality' and 'op_Inequality' for '==' and '!=' respectively (TODO: Link for all internal names for operator overloading).

 

class CompilerSugar

{

public static bool operator == (CompilerSugar cs1, CompilerSugar cs2)

{

return true;

}

public static bool operator !=(CompilerSugar cs1, CompilerSugar cs2)

{

return false;

}

}

.method public hidebysig specialname static

bool op_Equality(class CompilerSugar cs1,

class CompilerSugar cs2) cil managed

 

.method public hidebysig specialname static

bool op_Inequality(class CompilerSugar cs1,

class CompilerSugar cs2) cil managed

 

sizeof

In C# you can call 'sizeof' only for Value types, to get sizeof a class you need to call "System.Runtime.InteropServices.Marshal.SizeOf". When calling sizeof for premetive types (like int, long, float etc) compiler generates hard-coded values and remove call of 'sizeof' for optimization. 'int' used to be special in C/C++ but not in C# anymore. Unlike C/C++, 'int' is always 32-bit in C#. So its easy for compiler to just replace sizeof(int) to '4'. Although 'IntPtr' has same size issue which was there in C/C++ for 'int'. It is processor architecuture dependent. To get 'sizeof' a IntPtr or any other struct (other than premetive type) you have to use 'unsafe' block.

 

public class CompilerSugar

{

public CompilerSugar()

{

int s = 0;

s += sizeof(int);

s += sizeof(long);

s += sizeof(float);

unsafe

{

s += sizeof(IntPtr);

s += sizeof(CompilerSugarValueType);

}

}

}

struct CompilerSugarValueType

{

}

 

IL:

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// Code size 44 (0x2c)

.maxstack 2

.locals init ([0] int32 s)

IL_0000: ldarg.0

IL_0001: call instance void [mscorlib]System.Object::.ctor()

IL_0006: nop

IL_0007: nop

IL_0008: ldc.i4.0

IL_0009: stloc.0

IL_000a: ldloc.0

IL_000b: ldc.i4.4

IL_000c: add

IL_000d: stloc.0

IL_000e: ldloc.0

IL_000f: ldc.i4.8

IL_0010: add

IL_0011: stloc.0

IL_0012: ldloc.0

IL_0013: ldc.i4.4

IL_0014: add

IL_0015: stloc.0

IL_0016: nop

IL_0017: ldloc.0

IL_0018: sizeof [mscorlib]System.IntPtr

IL_001e: add

IL_001f: stloc.0

IL_0020: ldloc.0

IL_0021: sizeof CompilerSugarValueType

IL_0027: add

IL_0028: stloc.0

IL_0029: nop

IL_002a: nop

IL_002b: ret

} // end of method CompilerSugar::.ctor

 

 

 

Delegate (declaration):

For 'delegate' C# generates a class with bunch of functions. Class is declared as sealed and derived from 'System.MulticastDelegate'. Also addes 3 virtual functions (Invoke, BeginInvoke and EndInvoke) and a constructor:

 

public delegate int DelegateSugar(int i);

 

IL:

.class public auto ansi sealed DelegateSugar

extends [mscorlib]System.MulticastDelegate

 

.method public hidebysig specialname rtspecialname

instance void .ctor(object 'object',

native int 'method') runtime managed

 

.method public hidebysig newslot virtual

instance class [mscorlib]System.IAsyncResult

BeginInvoke(int32 i,

class [mscorlib]System.AsyncCallback callback,

object 'object') runtime managed

 

.method public hidebysig newslot virtual

instance int32 EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed

 

.method public hidebysig newslot virtual

instance int32 Invoke(int32 i) runtime managed

 

Calling Delegate:

To call a delegate First you need to create instance and pass pointer of function as constructor (Note: in C# 2.0 and above, now you can just type function name and compiler converts it back to old syntax). Calling delegate is also suger where it converts you function call to "delegate.Invoke"

 

public delegate int DelegateSugar(int i);

public class CompilerSugar

{

public CompilerSugar()

{

//This syntax is allowed in C#2.0, which is another compiler sugar

DelegateSugar ds = DelegateFunction;

/* C# 2.0 syntax for above line (while IL is exactly same)

DelegateSugar ds = new DelegateSugar(DelegateFunction);

*/

 

ds(10);

//Compiler converts this "ds(10)"into 'Invoke' call as following:

//ds.Invoke(10)

}

public int DelegateFunction(int i)

{

return 0;

}

}

 

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// Code size 32 (0x20)

.maxstack 3

.locals init ([0] class DelegateSugar ds)

IL_0000: ldarg.0

IL_0001: call instance void [mscorlib]System.Object::.ctor()

IL_0006: nop

IL_0007: nop

IL_0008: ldarg.0

IL_0009: ldftn instance int32 CompilerSugar::DelegateFunction(int32)

IL_000f: newobj instance void DelegateSugar::.ctor(object,

native int)

IL_0014: stloc.0

IL_0015: ldloc.0

IL_0016: ldc.i4.s 10

IL_0018: callvirt instance int32 DelegateSugar::Invoke(int32)

IL_001d: pop

IL_001e: nop

IL_001f: ret

} // end of method CompilerSugar::.ctor

 

 

Anonymous Delegate (without using local variable)

Anonymous delegates were first introduced in C# 2.0. For anonymous delegate compiler generates a function and field variable

public delegate int DelegateSugar(int i);

public class CompilerSugar

{

public CompilerSugar()

{

DelegateSugar d = delegate(int i)

{

return i;

};

}

}

 

.field private static class DelegateSugar 'CS$<>9__CachedAnonymousMethodDelegate1'

.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

 

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// Code size 51 (0x33)

.maxstack 3

.locals init ([0] class DelegateSugar d)

IL_0000: ldarg.0

IL_0001: call instance void [mscorlib]System.Object::.ctor()

IL_0006: nop

IL_0007: nop

IL_0008: ldsfld class DelegateSugar CompilerSugar::'CS$<>9__CachedAnonymousMethodDelegate1'

IL_000d: brtrue.s IL_0022

IL_000f: ldnull

IL_0010: ldftn int32 CompilerSugar::'<.ctor>b__0'(int32)

IL_0016: newobj instance void DelegateSugar::.ctor(object,

native int)

IL_001b: stsfld class DelegateSugar CompilerSugar::'CS$<>9__CachedAnonymousMethodDelegate1'

IL_0020: br.s IL_0022

IL_0022: ldsfld class DelegateSugar CompilerSugar::'CS$<>9__CachedAnonymousMethodDelegate1'

IL_0027: stloc.0

IL_0028: ldloc.0

IL_0029: ldc.i4.s 10

IL_002b: callvirt instance int32 DelegateSugar::Invoke(int32)

IL_0030: pop

IL_0031: nop

IL_0032: ret

} // end of method CompilerSugar::.ctor

 

.method private hidebysig static int32 '<.ctor>b__0'(int32 i) cil managed

{

.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

// Code size 7 (0x7)

.maxstack 1

.locals init ([0] int32 CS$1$0000)

IL_0000: nop

IL_0001: ldarg.0

IL_0002: stloc.0

IL_0003: br.s IL_0005

IL_0005: ldloc.0

IL_0006: ret

} // end of method CompilerSugar::'<.ctor>b__0'

 

 

 

Anonymous Delegate (using local variable)

If you are a C/C++ developer you can't image how anonymous delegate use local variable of calling function. In C# when a developer uses Anonymous Delegate which uses local variable of calling function then compiler treats it differently. Compiler generates a new nested class and declares all locally used variables as field variables of that new class.

 

public delegate int DelegateSugar(int i);

public class CompilerSugar

{

public CompilerSugar()

{

int localVariable = 10;

//Anonymous Delegate

DelegateSugar d = delegate(int i)

{

//using local variable of calling function

localVariable++;

return i + localVariable;

};

//Call for Delegate

d(10);

}

}

.class auto ansi sealed nested private beforefieldinit '<>c__DisplayClass1'

extends [mscorlib]System.Object

{

.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

} // end of class '<>c__DisplayClass1'

 

.field public int32 localVariable

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// Code size 7 (0x7)

.maxstack 8

IL_0000: ldarg.0

IL_0001: call instance void [mscorlib]System.Object::.ctor()

IL_0006: ret

} // end of method '<>c__DisplayClass1'::.ctor

 

.method public hidebysig instance int32 '<.ctor>b__0'(int32 i) cil managed

{

// Code size 28 (0x1c)

.maxstack 3

.locals init ([0] int32 CS$1$0000)

IL_0000: nop

IL_0001: ldarg.0

IL_0002: dup

IL_0003: ldfld int32 CompilerSugar/'<>c__DisplayClass1'::localVariable

IL_0008: ldc.i4.1

IL_0009: add

IL_000a: stfld int32 CompilerSugar/'<>c__DisplayClass1'::localVariable

IL_000f: ldarg.1

IL_0010: ldarg.0

IL_0011: ldfld int32 CompilerSugar/'<>c__DisplayClass1'::localVariable

IL_0016: add

IL_0017: stloc.0

IL_0018: br.s IL_001a

IL_001a: ldloc.0

IL_001b: ret

} // end of method '<>c__DisplayClass1'::'<.ctor>b__0'

 

Events:

Events are almost same as Delegates but compiler sugar creates some differences. For example, delegates are derived from MulticastDelegate but compiler syntax allows only 1 function per delegate while it allows multiple for Events. Everytime one attaches method for event, compiler calls Delegate.Combine method. Compiler also adds 'add_<eventname>' and 'remove_<eventname>'.

 

public delegate int DelegateSugar(int i);

public class CompilerSugar

{

public event DelegateSugar EventSugar;

public CompilerSugar()

{

EventSugar += OnEventSugar;

EventSugar(10);

}

public int OnEventSugar(int i)

{

return i;

}

}

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// Code size 58 (0x3a)

.maxstack 8

IL_0000: ldarg.0

IL_0001: call instance void [mscorlib]System.Object::.ctor()

IL_0006: nop

IL_0007: nop

IL_0008: ldarg.0

IL_0009: dup

IL_000a: ldfld class DelegateSugar CompilerSugar::EventSugar

IL_000f: ldarg.0

IL_0010: ldftn instance int32 CompilerSugar::OnEventSugar(int32)

IL_0016: newobj instance void DelegateSugar::.ctor(object,

native int)

IL_001b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,

class [mscorlib]System.Delegate)

IL_0020: castclass DelegateSugar

IL_0025: stfld class DelegateSugar CompilerSugar::EventSugar

IL_002a: ldarg.0

IL_002b: ldfld class DelegateSugar CompilerSugar::EventSugar

IL_0030: ldc.i4.s 10

IL_0032: callvirt instance int32 DelegateSugar::Invoke(int32)

IL_0037: pop

IL_0038: nop

IL_0039: ret

} // end of method CompilerSugar::.ctor

 

.method public hidebysig specialname instance void

add_EventSugar(class DelegateSugar 'value') cil managed synchronized

{

// Code size 24 (0x18)

.maxstack 8

IL_0000: ldarg.0

IL_0001: ldarg.0

IL_0002: ldfld class DelegateSugar CompilerSugar::EventSugar

IL_0007: ldarg.1

IL_0008: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,

class [mscorlib]System.Delegate)

IL_000d: castclass DelegateSugar

IL_0012: stfld class DelegateSugar CompilerSugar::EventSugar

IL_0017: ret

} // end of method CompilerSugar::add_EventSugar

 

.method public hidebysig specialname instance void

remove_EventSugar(class DelegateSugar 'value') cil managed synchronized

{

// Code size 24 (0x18)

.maxstack 8

IL_0000: ldarg.0

IL_0001: ldarg.0

IL_0002: ldfld class DelegateSugar CompilerSugar::EventSugar

IL_0007: ldarg.1

IL_0008: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate,

class [mscorlib]System.Delegate)

IL_000d: castclass DelegateSugar

IL_0012: stfld class DelegateSugar CompilerSugar::EventSugar

IL_0017: ret

} // end of method CompilerSugar::remove_EventSugar

 

.event DelegateSugar EventSugar

{

.addon instance void CompilerSugar::add_EventSugar(class DelegateSugar)

.removeon instance void CompilerSugar::remove_EventSugar(class DelegateSugar)

} // end of event CompilerSugar::EventSugar

 

Yield:

Yield is probably biggest compiler sugar in C# and unknown feature. If you have a 'yield' in your function then compiler generates a new nested class which implments "IEnumerable<T>, IEnumerable, IEnumerator<T>, IEnumerator, IDisposable" and function local variables and 'this' are declared as field variable of that newly generated class.

 

public class CompilerSugar

{

public IEnumerable<int> YieldSugar(int num)

{

//Returns all the divisble numbers of num.

//lets say if num is 20, this function will return

//2, 4, 5, 10

for (int i = 2; i < num; i++)

if (num % i == 0)

yield return i;

}

}

Since it generates alot of code so copy/pasting just metadata (using Reflector) instead of whole IL:

[CompilerGenerated]

private sealed class <YieldSugar>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable

{

// Fields

private int <>1__state;

private int <>2__current;

public int <>3__num;

public CompilerSugar <>4__this;

private int <>l__initialThreadId;

public int <i>5__1;

public int num;

// Methods

[DebuggerHidden]

public <YieldSugar>d__0(int <>1__state);

private bool MoveNext();

[DebuggerHidden]

IEnumerator<int> IEnumerable<int>.GetEnumerator();

[DebuggerHidden]

IEnumerator IEnumerable.GetEnumerator();

[DebuggerHidden]

void IEnumerator.Reset();

void IDisposable.Dispose();

// Properties

int IEnumerator<int>.Current { [DebuggerHidden] get; }

object IEnumerator.Current { [DebuggerHidden] get; }

}

 

It also changes code of your actual function, now your 'YieldSugar' function code will look like (using Reflector)

public IEnumerable<int> YieldSugar(int num)

{

<YieldSugar>d__0 d__ = new <YieldSugar>d__0(-2);

d__.<>4__this = this;

d__.<>3__num = num;

return d__;

}

 

 

 

 

TODO:

ForEach

constant values within function

readonly variables

typeof (it calls System.Type.GetTypeFromHandle)

Boxing/Unboxing

fully qualified name (e.g. Console.WriteLine is IL_0001: call void [mscorlib]System.Console::WriteLine())