Methods - Expert C# 5.0: with .NET 4.5 Framework (2013)

Expert C# 5.0: with .NET 4.5 Framework (2013)

CHAPTER 4

images

Methods

This chapter will discuss the following C# methods: instance, static, anonymous, and extension. In doing so, you’ll learn how the this keyword relates to these methods and how the CLR passes value for the this keyword used as parameter in runtime by examining data on the stack.

You will also explore how the anonymous method works when there is external variable reference in it and when there is no external variable referenced from it. Additionally, you will find out how the this keyword is used in both circumstances.

Finally, you will learn about the compile time and runtime behavior of the extension methods and how the C# compiler eliminates the this keyword from the extension method signature.

Instance and Static Method with this Keyword

The this keyword refers to the current instance of the class, and it is permitted only in the block of an instance constructor, an instance method, or an instance accessor (all of these were covered in Chapter 1). When the this keyword is used in the instance constructor or in an instance method, the CLR treats it as the value of the object for which the constructor, instance method, or the accessor was invoked. For example, if you have a type T and it has an instance method M or instance constructor C that uses the this keyword, in runtime this this keyword from the M and C refers to the object O which is the instance of the type T for that time and from where M and C tried to access this keyword.

This is only possible in the instance method or instance constructor. However, it is not possible to use the this keyword with the static method because you cannot instantiate an instance of the static class. In runtime, for the instance method or instance constructor, the CLR passes an extra value for the invocation, which is the value of the this parameter. The value of the this refers to the instance of the type for which the method or constructor is being invoked. In the following section, you will learn more about this by looking into the runtime stack information for an instance and static method of a type.

The program in Listing 4-1 uses an instance and static method to do an add operation of two int values to show how C# compiler includes the this keyword as a parameter to the instance method behind the scenes. Static method does not have the this keyword as a parameter.

Listing 4-1. An Example of the Instance and Static Methods

using System;

namespace Ch04
{
class Program
{
static void Main(string[] args)
{
int valueOfA = 10, valueOfB = 20;
Calculator calculator = new Calculator();

Console.WriteLine("The sum using instance method \t{0} \nand using static method
\t{1}",
calculator.Add(valueOfA, valueOfB),
CalculatorAsStatic.Add(valueOfA, valueOfB));
}
}

public class Calculator
{
public int Add(int a, int b) /* An extra this parameter will be added in behind the
* scene to the Parameters section of Add
* methods stack */
{
return a + b;
}
}

public static class CalculatorAsStatic
{
public static int Add(int a, int b) /* No extra this parameter will be added to the
* Parameters section of Add methods
* stack */

{
return a + b;
}
}
}

This program will produce the following output:

The sum using instance method 30
and using static method 30

Memory Information while Running an Instance Method

Figure 4-1 shows that in the PARAMETERS section of the Add method’s stack there is an extra this parameter that holds the object reference where the Add method belongs. The value of the this parameter is passed by the Main method. For example, the address 0x0180b6dc from the LOCALS section of the Mainmethod refers to the instance of the Calculator class.

images

Figure 4-1. Instance method and this keyword

Let’s see the stack information of the Main method of the Program class and Add method of the Calculator class while debugging Listing 4-1 using the windbg.exe tool:

0:000> !clrstack -a
OS Thread Id: 0x434 (0)
Child SP IP Call Site
002aeed8 004201b9 Ch04.Calculator.Add(Int32, Int32) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch04\Program.cs @ 21]
PARAMETERS:
this (0x002aeee0) = 0x0180b6dc /* refers to the Calculator object as this*/
a (0x002aeedc) = 0x0000000a
b (0x002aeeec) = 0x00000014
LOCALS:
0x002aeed8 = 0x0000001e

002aeef0 004200db Ch04.Program.Main(System.String[]) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch04\Program.cs @ 11]
PARAMETERS:
args (0x002aef20) = 0x0180b63c
LOCALS:
0x002aef1c = 0x0000000a
0x002aef18 = 0x00000014
0x002aef0c = 0x0180b6dc /* refers to the Calculator object */

002af154 5a8a21db [GCFrame: 002af154]

Memory Information while Running a Static Method

In Figure 4-2, you can see that in the PARAMETERS section of the Add method there is not an extra this parameter. The Add method of the CalculatorAsStatic class has only a and b parameters in the PARAMETERS section.

images

Figure 4-2. Static method and the this keyword

Let’s see the stack information of the Main method of the Program class and Add method of the CalculatorAsStatic class while debugging Listing 4-1 using the windbg.exe tool:

0:000> !clrstack -a
OS Thread Id: 0x434 (0)
Child SP IP Call Site
002aeedc 00420209 Ch04.CalculatorAsStatic.Add(Int32, Int32) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch04\Program.cs @ 29]
PARAMETERS:
a (0x002aeee4) = 0x0000000a
b (0x002aeee0) = 0x00000014
LOCALS:
0x002aeedc = 0x0000001e

002aeef0 00420106 Ch04.Program.Main(System.String[]) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch04\Program.cs @ 11]
PARAMETERS:
args (0x002aef20) = 0x0180b63c
LOCALS:
0x002aef1c = 0x0000000a
0x002aef18 = 0x00000014
0x002aef0c = 0x0180b6dc

002af154 5a8a21db [GCFrame: 002af154]

You have now learned about the instance and static methods in relation to the this keyword. Instance and static are the common kinds of methods used in the program. This sometimes requires writing a method that does not do much. Therefore, instead of writing separate methods, you can use an anonymous method that gives you the option to write an inline method that does not have any valid names. In the next section, you will learn more about this and explore how the this keyword relates to the anonymous method.

Anonymous Method

An anonymous function is an expression that represents an inline method definition in a type. It is convertible to a compatible delegate.

The conversion of an anonymous function depends on the target type, for example:

· If it is a delegate type, the conversion evaluates to a delegate value, referencing the method that the anonymous function defines.

· If it is an expression tree type, the conversion evaluates to an expression tree that represents the structure of the method as an object structure.

You can use anonymous functions in two ways:

· Lambda expressions

· Anonymous method

The anonymous method offers a simple and elegant solution in many situations, such as when using Array.ForEach<T>. Typically when you see a method that accepts a delegate as a parameter, you could use the anonymous method. The lambda expression is an anonymous function can be used to create delegates or expression tree types. Listing 4-2 provides an example of the anonymous method.

Listing 4-2. An Example of the Anonymous Method

using System;

namespace Ch04
{
class Program
{
static void Main(string[] args)
{
int valueOfA = 10,
valueOfB = 20,
increment = 2; /* Used as the external or captured variable
* for the anonymous method */
Calculator calculator = new Calculator();

Console.WriteLine("The sum is \t:{0}", calculator.Add
(delegate(int a, int b) /* Anonymous method declaration */
{
return a + b + increment; /* increment is the outer variable */
}, valueOfA, valueOfB));
}
}

public class Calculator
{
public delegate int Adder(int a, int b);

public int Add(Adder adder, int a, int b)
{
return adder(a, b);
}
}
}

This program will produce the following output:

The sum is :32

In Listing 4-2, the Calculator class has a method Add, which takes Adder, a type of delegate, along with two other int type parameters. The Adder is declared as a delegate, which takes two int type inputs and returns an int. The Main method of the Program class is called the Add method of the Calculatorclass by passing an anonymous method defined by using the delegate.

In Compile Time

You can see in Listing 4-2 that the anonymous method is defined using the delegate type. The Add method of the Calculator class accepts a delegate input, and the Main method of the Program class passes a block of code as input to the Add method. This block of code is the anonymous method used in C#.

The C# compiler compiles the anonymous method as follows:

· When an external variable or capture variable is used in the anonymous method body, the C# compiler generates a type with a method that contains the original body of the anonymous method.

· If the anonymous method does not use a capture variable, the C# compiler generates a method using the code used for the anonymous method in the same class where the original anonymous method was defined.

These two scenarios are detailed in the following sections.

External Variable Referenced from the Anonymous Method

If you access any variable defined outside the anonymous method body, the C# compiler then compiles the anonymous method as follows:

· It generates a new class, for example, <>c__DisplayClass1, as in Listing 4-2.

· It generates a method that contains the body of the anonymous method, as defined in Listing 4-2.

· It adds the external variable (accessed from the anonymous method) as the field of the <>c__DisplayClass1 class.

· The caller of the anonymous method instantiates an instance of the <>c__DisplayClass1 class and loads the function pointer of the autogenerated method of the anonymous method block defined in the <>c__DisplayClass1 class. The function pointer of the anonymous method is defined in the<>c__DisplayClass1 used to instantiate the instance of the Adder delegate and passes it as an argument to the Add method of the Calculator class.

Figure 4-3 demonstrates the anonymous method compilation using the C# compiler. You can see that the C# compiler compiles the anonymous method into a class <>c__DisplayClass1, which contains a method <Main>b__0 that contains the body of the anonymous method you defined in the Mainmethod of the Program class.

images

Figure 4-3. Anonymous method in compile time

The C# compiler generates the Program class in a way that it can use the <Main>b__0 method it generates in the <>c__DisplayClass1 class.

To explore more about this, let’s examine the coding in Listing 4-3, which is the decompiled IL version of Listing 4-2. It was decompiled using the .NET Reflector tool.

Listing 4-3. Decompiled IL of Listing 4-2

.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: call instance void [mscorlib]System.Object::.ctor()
L_0006: ret
}
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 5
.locals init (
[0] int32 valueOfA,
[1] int32 valueOfB,
[2] class Ch04.Calculator calculator,
[3] class Ch04.Program/<>c__DisplayClass1 CS$<>8__locals2)

/* Instantiates an instance of the <>c__DisplayClass1 class */
L_0000: newobj instance void Ch04.Program/<>c__DisplayClass1::.ctor()
L_0005: stloc.3
L_0006: nop
L_0007: ldc.i4.s 10
L_0009: stloc.0
L_000a: ldc.i4.s 20
L_000c: stloc.1
L_000d: ldloc.3
L_000e: ldc.i4.2

/* Adds the value for the outer variable increment*/
L_000f: stfld int32 Ch04.Program/<>c__DisplayClass1::increment
L_0014: newobj instance void Ch04.Calculator::.ctor()
L_0019: stloc.2
L_001a: ldstr "The sum is \t:{0}"
L_001f: ldloc.2
L_0020: ldloc.3

/* Loads the function pointer for the method <Main>b__0 generated
* by the C# compiler for the anonymous method block */
L_0021: ldftn instance int32 Ch04.Program/<>c__DisplayClass1::<Main>b__0(int32, int32)

/* Instantiates an instance of the Adder delegate using the
* function pointer loads in L_0021 */
L_0027: newobj instance void Ch04.Calculator/Adder::.ctor(object, native int)
L_002c: ldloc.0
L_002d: ldloc.1

/* Calls the Add method of the Calculator class by passing the delegate instance
* instantiated in L_0027 and the value stored at position 0 (valueOfA) and
* 1 (valueOfB) in the Locals section of the Main method */
L_002e: callvirt instance int32 Ch04.Calculator::Add(class Ch04.Calculator/Adder, int32, int32)
L_0033: box int32
L_0038: call void [mscorlib]System.Console::WriteLine(string, object)
L_003d: nop
L_003e: nop
L_003f: ret
}

/* The C# compiler automatically generates the anonymous method block as method
* embedded into the auto generated class <>c__DisplayClass1 */
.class auto ansi sealed nested private beforefieldinit <>c__DisplayClass1
extends [mscorlib]System.Object
{
{ /* Code removed */ }
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{ /* Code removed */ }

.method public hidebysig instance int32 <Main>b__0(int32 a, int32 b) cil managed
{
.maxstack 2
.locals init (
[0] int32 num)
L_0000: nop
L_0001: ldarg.1
L_0002: ldarg.2
L_0003: add
L_0004: ldarg.0
L_0005: ldfld int32 Ch04.Program/<>c__DisplayClass1::increment
L_000a: add
L_000b: stloc.0
L_000c: br.s L_000e
L_000e: ldloc.0
L_000f: ret
}
.field public int32 increment

}
}

Memory Information with External Variable Referenced from the Anonymous Method

Listing 4-3 demonstrates that the C# compiler automatically generates the <>c__DisplayClass1 class for the anonymous method. Inside this class it defined a method <Main>b__0 with the same body as the original anonymous method defined in the Main method of the Program class. In L_0000 of Listing 4-3, an instance of the <>c__DisplayClass1 has been instantiated. The CLR loads the function pointer of the <Main>b__0 method from the instance of the <>c__DisplayClass1 class in L_0021. This function pointer will use L_0027 to instantiate an instance of the Adder delegate to pass it as a parameter to the Add method of the instance of Calculator class instantiated in L_0014. Figure 4-4 demonstrates how the CLR handles the anonymous method in runtime.

images

Figure 4-4. Anonymous Method in Runtime

Let’s examine the stack information while debugging Listing 4-2 using the windbg.exe tool, where you can see the stack information of the Add and Main methods:

0:000> !clrstack -a
OS Thread Id: 0x1358 (0)
Child SP IP Call Site
002af234 003901f0 Ch04.Calculator.Add(Adder, Int32, Int32) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch04\Program.cs @ 26]
PARAMETERS:
this (<CLR reg>) = 0x01d3b688
adder (<CLR reg>)= 0x01d3b694 /* Which contains reference of the
* 0x01d3b67c in the _target field*/
a (0x002af23c) = 0x0000000a
b (0x002af238) = 0x00000014
LOCALS:
<no data>

002af240 00390127 Ch04.Program.Main(System.String[]) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch04\Program.cs @ 13]
PARAMETERS:
args (0x002af270)= 0x01d3b63c
LOCALS:
0x002af26c = 0x0000000a
0x002af268 = 0x00000014
0x002af260 = 0x01d3b688
0x002af25c = 0x01d3b67c /* Instance of the <>c__DisplayClass1 */

002af4a8 5a8a21db [GCFrame: 002af4a8]

Let’s see the object information located in the 0x01d3b67c of the heap used in the LOCALS section of the Main method, which is the address of the <>c__DisplayClass1 class:

0:000> !dumpobj 0x01d3b67c
Name: Ch04.Program+<>c__DisplayClass1
MethodTable: 001438bc
EEClass: 001414fc
Size: 12(0xc) bytes
File: J:\Book\ExpertC#2012\SourceCode\BookExamples\Ch04\bin\Debug\Ch04.exe
Fields:
MT Field Offset Type VT Attr Value Name
54ff2978 4000001 4 System.Int32 1 instance 2 increment

The address 0x01d3b694 is used for the Adder variable of the Add method of the Calculator class, which refers to the instance of the delegate Adder from the Calculator class, as shown below:

0:000> !dumpobj 0x01d3b694
Name: Ch09.Calculator+Adder
MethodTable: 002438ac
EEClass: 002413c8
Size: 32(0x20) bytes
File: J:\Book\ExpertC#2012\SourceCode\BookExamples\Ch09\bin\Debug\Ch09.exe
Fields:
MT Field Offset Type VT Attr Value Name
5654bba8 400002d 4 System.Object 0 instance 0x01d3b67c _target
5654bba8 400002e 8 System.Object 0 instance 00000000 _methodBase
5654ac2c 400002f c System.IntPtr 1 instance 24c088 _methodPtr
5654ac2c 4000030 10 System.IntPtr 1 instance 0 _methodPtrAux
5654bba8 4000031 14 System.Object 0 instance 00000000 _invocationList
5654ac2c 4000032 18 System.IntPtr 1 instance 0 _invocationCount

The CLR also passes the value for the this parameter to the anonymous method, For example, if you explore the stack information of the <Main>b__0 method from the Program+<>c__DisplayClass1 class in runtime you will see that the CLR passes the value of the this parameter to the <Main>b__0 method in addition to the other parameters. To do so, a breakpoint needs to be set using the windbg.exe tool while debugging Listing 4-2, using the following commands:

!bpmd Ch04.exe Ch04.Program.Main
!bpmd Ch04.exe Ch04.Program+<>c__DisplayClass1.<Main>b__0

After setting the breakpoint, execute the !clrstack –a command in the windbg.exe tool, which will show you the detailed stack information of the <Main>b__0 method from the Program+<>c__DisplayClass1 class:

0:000> !clrstack -a
OS Thread Id: 0x11ec (0)
Child SP IP Call Site
0022eda4 00510257 Ch04.Program+<>c__DisplayClass1.<Main>b__0(Int32, Int32) [J:\Book\
ExpertC#2012\SourceCode\BookExamples\Ch04\Program.cs @ 17]
PARAMETERS:

/* this pointing to the instance of the Ch04.Program+<>c__DisplayClass1*/
this (0x0022eda4) = 0x01d3b67c

a (0x0022edac) = 0x0000000a
b (0x0022edb8) = 0x00000014
LOCALS:
0x0022eda8 = 0x00000000

0022edbc 0051020e Ch04.Calculator.Add(Adder, Int32, Int32) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch04\Program.cs @ 29]
PARAMETERS:
this (0x0022edc8) = 0x01682480
adder (0x0022edbc) = 0x0168248c
a (0x0022edd8) = 0x0000000a
b (0x0022edd4) = 0x00000014
LOCALS:
0x0022edc4 = 0x00000000

0022eddc 00510119 Ch04.Program.Main(System.String[]) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch04\Program.cs @ 15]
PARAMETERS:
args (0x0022ee0c) = 0x01682434
LOCALS:
0x0022ee08 = 0x0000000a
0x0022ee04 = 0x00000014
0x0022edfc = 0x01682480
0x0022edf8 = 0x01682474

0022ef9c 53a43dd2 [GCFrame: 0022ef9c]

The stack information of the <Main>b__0 method from the Program+<>c__DisplayClass1 class shows that the CLR passes the value for the this parameter exactly the same as in the instance method we explored earlier.

The following section will examine the anonymous method, which does not have any external variable references. You will also learn how the CLR passes the value for the this parameter for the anonymous method.

External Variable Not Referenced from the Anonymous Method

Let’s modify the anonymous method body defined in Listing 4-2, as shown in Listing 4-4. In this modified version, the anonymous method does not use any external or captured variable.

Listing 4-4. Modified Anonymous Method

static void Main(string[] args)
{
int valueOfA = 10, valueOfB = 20;
Calculator calculator = new Calculator();

Console.WriteLine("The sum is \t:{0}", calculator.Add
(delegate(int a, int b)
{
return a + b;
}, valueOfA, valueOfB));
}

If you do not access any variable defined outside the anonymous method body, the C# compiler compiles the anonymous method, as demonstrated here:

· It generates a method <Main>b__0 (for example, based on Listing 4-4), which contains the body of the anonymous method defined in Listing 4-4.

· The caller of the anonymous method will load the function pointer for the anonymous method <Main>b__0, and using this function pointer, the CLR instantiates an instance of the delegate Adder and passes it to the Add method of the Calculator class.

To explore more about this, Listing 4-5 presents the decompiled IL version of Listing 4-4 using the .NET Reflector tool.

Listing 4-5. Decompiled IL Version of Listing 4-4

.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
/* Code removed */
/* The C# compiler generates the anonymous method block as method */
.method private hidebysig static int32 <Main>b__0(int32 a, int32 b) cil managed
{
/* Code removed */
.maxstack 2
.locals init (
[0] int32 CS$1$0000)
L_0000: nop
L_0001: ldarg.0
L_0002: ldarg.1
L_0003: add
L_0004: stloc.0
L_0005: br.s L_0007
L_0007: ldloc.0
L_0008: ret
}

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 5
.locals init (
[0] int32 valueOfA,
[1] int32 valueOfB,
[2] class Ch04.Calculator calculator)
L_0000: nop
L_0001: ldc.i4.s 10
L_0003: stloc.0
L_0004: ldc.i4.s 20
L_0006: stloc.1

/* Instantiates an instance of the Calculator class */
L_0007: newobj instance void Ch04.Calculator::.ctor()
L_000c: stloc.2
L_000d: ldstr "The sum is \t:{0}"
L_0012: ldloc.2

L_0013: ldsfld class Ch04.Calculator/Adder Ch04.Program::
CS$<>9__CachedAnonymousMethodDelegate1
L_0018: brtrue.s L_002d
L_001a: ldnull

/* Loads the function pointer for the method <Main>b__0 generates
* by the C# compiler for the anonymous method block */
L_001b: ldftn int32 Ch04.Program::<Main>b__0(int32, int32)

/* Instantiates an instance of the Adder delegate using the
* function pointer load in L_0021 */
L_0021: newobj instance void Ch04.Calculator/Adder::.ctor(object, native int)

L_0026: stsfld class Ch04.Calculator/Adder Ch04.Program::
CS$<>9__CachedAnonymousMethodDelegate1
L_002b: br.s L_002d
L_002d: ldsfld class Ch04.Calculator/Adder Ch04.Program::
CS$<>9__CachedAnonymousMethodDelegate1
L_0032: ldloc.0
L_0033: ldloc.1
/* Calls the Add method using the delegate instance instantiated
* in the L_0027 and the value stored at position 0 and 1 in the
* Locals section of the Main method */
L_0034: callvirt instance int32 Ch04.Calculator::Add(class Ch04.Calculator/Adder, int32,
int32)
L_0039: box int32
L_003e: call void [mscorlib]System.Console::WriteLine(string, object)
L_0043: nop
L_0044: ret
}
.field private static class Ch04.Calculator/Adder CS$<>9__CachedAnonymousMethodDelegate1
{ /* Code removed */ }
}

Listing 4-5 demonstrates that C# compiler automatically generates a method <Main>b__0 with the contents of the anonymous method defined in Listing 4-4. The CLR loads the function pointer of <Main>b__0 in L_001b and passes this function pointer as an argument to the Adder delegate in L_0021. The CLR uses this delegate instance in L_0026 to store this into the CS$<>9__CachedAnonymousMethodDelegate1 field of the Program class. The CLR passes the value of the CS$<>9__CachedAnonymousMethodDelegate1 along with the other two arguments of the Add method of the instance of the Calculator class instantiated inL_0007.

Listing 4-5 also shows that the anonymous method in this circumstance is compiled as a static method. As a result, there will not be any this parameter for the anonymous method; for example, in this circumstance it is the <Main>b__0 method, as shown in Listing 4-5.

So far we have examined the instance method, extension method, and anonymous method, but when you define these methods for a type, they all have to reside in the same assembly where the type was defined. The extension method does not require defining the method in the same assembly for which you will write the method.

Extension Method

In .NET, extension methods provide a mechanism by which you can add functionality to a type without modifying it to avoid the risk of breaking code in existing applications. You can also add additional methods in the interface without altering the existing class libraries.

So the extension method allows you to extend the existing compiled types to have a new functionality without needing to directly update the type. It is quite helpful when you need to inject new functionality into types where you do not have an existing code base. It can also be useful when you need a class to support a set of members, but it cannot modify the original type declaration. Using the extension method, you can add functionality to compiled types while providing the illusion that these methods were there all along.

To extend a type’s functionality using the extension method technique provided by the C#, you need to do the following:

· Make a static class.

· Add a static method in this static class with the appropriate functionality. In the parameter list of this new method, add an extra parameter this along with the type name for which this method will extend the functionality. For example, GetLastCharacter method in Listing 4-6 extends functionality for the string type.

In Listing 4-6, an extension method is defined for the string type. This extension method is used to determine the last character of a word whose type is string.

Listing 4-6. An Example of the Extension Method

using System;

namespace Ch04
{
class Program
{
static void Main(string[] args)
{
string data = "abcd";
Console.WriteLine("{0}", data.GetLastCharacter()); /* Calls extension defined for the string type. */
}
}
public static class Ch04_ExtensionMethods /* A Static class defined */
{
public static string GetLastCharacter(this string data)
/* A static method with the parameter
* this along with the type name string */
{
if (data == null || data == string.Empty)
return string.Empty;
return data[data.Length - 1].ToString();
}
public static Int32 GetNum(this Int32 dd)
{
return dd;
}
}
}

The program will produce the following output:

d

The GetLastCharacter extension method determines the last character from the input data if the data are not null or do not contain an empty value. In Listing 4-6, a static class Ch04_ExtensionMethods is defined and a static method GetLastCharacter is added. The first parameter contains the this keyword along with a parameter of the type that is going to be extended, in this case it is string.

When you define any extension method for a type, it shows Visual Studio’s IntelliSense along with the standard methods of that type. In Figure 4-5, you see that the GetLastCharacter extension method for the string type shows Visual Studio’s IntelliSense along with other standard methods of the string type.

images

Figure 4-5. Extension methods in Visual studio's IntelliSense

You have seen how to design an extension method, and in the following sections you will explore more about the internal working of the extension method, for example, how the C# compiler translates the definition of the extension method into a static method and what happens if the thiskeyword is used to define the extension method.

Internal Work of the Extension Method

The C# compiler rewrites the extension methods by removing the this keyword from the extension methods signature and adding an ExtensionAttribute to it. The compiler also changes the extension method’s caller code with the same syntax as that of the static method call.

Figure 4-6 demonstrates the C# compiler’s compilation process in the extension method and calling convention.

images

Figure 4-6. Extension methods working behind the scenes

You can see from Figure 4-6 that the C# compiler compiles the Ch04_ExtensionMethods class as a static class that contains all the extension methods defined in the original class except for the this keyword from the methods parameter list, which is eliminated by the C# compiler in the compile time.

To explore more about this, let’s examine Listing 4-7, which is the decompiled IL version of Listing 4-6.

Listing 4-7. IL Code of the Extension Methods and Calling Class

.class public abstract auto ansi sealed
beforefieldinit Ch04_ExtensionMethods
extends [mscorlib]System.Object
{
/* Code removed */
/* The Original GetLastCharacter decompiled as a static method and removed the
* this keyword from the parameter list*/
.method public hidebysig static string GetLastCharacter(string data) cil managed
{
.custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.
ctor()
.maxstack 3
.locals init (
[0] string CS$1$0000,
[1] bool CS$4$0001,
[2] char CS$0$0002)
/* Code removed */
}

/* The Original GetLastCharacter decompiled as a static method and removed the
* this keyword from the parameter list*/
.method public hidebysig static int32 GetNum(int32 dd) cil managed
{
.custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
.maxstack 1
.locals init (
[0] int32 CS$1$0000)
/* Code removed */
}

}

In Listing 4-7, you can see that the C# compiler removed the this keyword from the GetLastCharacter method signature and declares a static method, which accepts a string type input and also defines inside a static class, as shown in Listing 4-7.

The C# compiler also changed the code from where the extension method GetLastCharacter is called. From Listing 4-8 you can see that the GetLastCharacter method from the Main method is called as Ch04.Ch04_ExtensionMethods::GetLastCharacter(string), which is the syntax of the static method call.

Listing 4-8. Decompiled IL code of the Main Method from Listing 4-6 Using the .NET Reflector Tool

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 2
.locals init (
[0] string data)
L_0000: nop
L_0001: ldstr "abcd"
L_0006: stloc.0
L_0007: ldstr "{0}"
L_000c: ldloc.0

/* GetLastCharacter method is called as a static method */
L_000d: call string Ch04.Ch04_ExtensionMethods::GetLastCharacter(string)

L_0012: call void [mscorlib]System.Console::WriteLine(string, object)
L_0017: nop
L_0018: ret
}

In L_000d of Listing 4-8, GetLastCharacter method is called as the static method. In L_000c, the CLR loads the local variable stored at the position 0 from the method state description table to the evaluation stack. In L_000d, CLR calls a static method GetLastCharacter of the Ch04_ExtensionMethods class by passing the data (in L_000c) from the evaluation stack as the argument value. The extension method is another design-time syntactic sugar to make the development easier, but in runtime, it behaves exactly like the static class and static method.

Extension Method and Resolution

When you use an extension method from a different namespace, it needs to define the namespace specifically, as demonstrates in Listing 4-9.

Listing 4-9. Extension Method and Resolution

namespace Ch04
{
using System;

/* CH04_Extensions has to declare here otherwise compiler-time error occurred. */
using Ch04_Extensions;

class Program
{
static void Main(string[] args)
{
string data = "abcd";
Console.WriteLine("{0}", data.GetLastCharacter());
}
}
}

/* Extension method defined in the Ch04_ExtensionMethods class which reside
* in the Ch04_Extensions namespace */
namespace Ch04_Extensions
{
public static class Ch04_ExtensionMethods
{
public static string GetLastCharacter(this string data)
{
if (data == null || data == string.Empty) return string.Empty;
return data[data.Length - 1].ToString();
}
}
}

This program will produce following output:

d

Extension Method and Custom Class

You can also extend a custom-defined type. For example, in Listing 4-10, Calculator class has been extended in the Ch04_ExtensionMethods class. The extended functionality using extension method is named Sub. Let’s see how this works, as shown in Listing 4-10.

Listing 4-10. Extending Custom Class

using System;

namespace Ch04
{
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
Console.WriteLine(calculator.Sub(10, 5));
}
}
public static class Ch04_ExtensionMethods /* A Static class defined */
{
public static int Sub(this Calculator calculator, int a, int b)
{
return a > b ? a - b : b - a;
}
}

public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
}

The Listing 4-10 produces the following output:

5

Summary

In this chapter we have learned about the instance method, static method, anonymous method, and extension method in C# by examining the stack information. You explored how the anonymous method works when there is or is not an external variable referenced in the anonymous method. In addition, you learned how the this keyword is used in both circumstances. Finally, you learned about the extension method by looking into the compile time and runtime behavior of the extension methods. Learning the internal mechanism of these methods will give you a solid understanding of how these methods work behind the scenes and also help you to write better code.