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())