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

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

CHAPTER 7

images

Delegate

This chapter will discuss delegates. First, we will examine the C# delegate compared to the function pointer in C language, which will give you a basic idea of the similarity between the function pointer in C and delegate in .NET. This will also build a background knowledge about the function pointer, which will help you understand the internal working of delegates. Finally, you will explore generic delegates such as Func and Action in .NET by examining how the C# compiler takes care of the Func and Action delegates.

Understanding Delegates

In .NET, a delegate defines a reference type that can be used to encapsulate a method such as static or an instance method with a specific signature. When you declare a variable of delegate type, the CLR stores a method pointer in it as the method pointer value, and through that variable ofdelegate type, you can invoke the assigned method from the appropriate places. The CLR maintains the method pointer behind the scenes for the delegate type, so before diving deeper into the workings of the delegate, let’s revisit the pointer concept.

The pointer is nothing but a memory address stored in a variable that points back to the value the address is pointing to and is also used to point to a method. The pointer is all about accessing or updating the value of a variable or function by accessing the address of that variable or function using the & and * symbols in a program.

The pointer can hold a variable’s memory location, such as:

Variable Pointer (VP) =a memory location P store in a variable V then V is called the pointer of
P or V is pointing to P

It can also hold the location of a function or method as a value. A function pointer declared with a syntax would be:

Function Pointer (FP) = ReturnType ( *NameOfFunctionPointer ) (InputType)

This function pointer holds an address of a function whose signature matches the function pointer signature defined as FP. When it stores the variable location defined as VP, you can call it a variable pointer, and when it stores the function’s location, you can call it a function or method pointer. Figure 7-1 demonstrates variable and function pointers.

images

Figure 7-1. Pointer and memory location

To explore more about the variable and function pointers, let’s examine the code in Listing 7-1, where a variable pointer and a function pointer are declared, using C programming language, to describe the pointer concept of a variable and a function.

Listing 7-1. Example of the Function Pointer in C

#include "stdafx.h"

int addition( int a )
{
return a+10;
}

int pointerTest( int* a )
{
*a=100; /* change the value of the pointer variable a pointing to*/
return *a; /* return the value of the pointer variable a pointing to*/
}

int _tmain(int argc, _TCHAR* argv[])
{
int aVariable =10; /* declare a variable aVariable of type int */
printf("%d\n", pointerTest(&aVariable)); /* aVariable's address pass to the pointerTest
* function */
int (*functionPointerInC)(int); /* declare a Function Pointer which accept and
* return int.*/
functionPointerInC = &addition; /* Assign a function's
* address to the Function Pointer */
printf("%d",(*functionPointerInC)(1) ); /* Invoke the Function pointer */
return 0;
}

This program produces the following output:

In Listing 7-1, three functions, addition, which takes an input, pointerTest, which takes a variable pointer as input, and _tmain, which takes two inputs, execute the program. In the _tmain method, a variable called aVariable of type int with value 10 is declared and the pointerTest function is called by passing the address of the aVariable variable.

The addition method is used in Listing 7-1 to assign the function pointer functionPointerInC by assigning the address of addition function (&addition refers to the address or location of the addition function).

In runtime, the contents or value and address or location of the variable aVariable in the _tmain and pointerTest method will be as shown as in Table 7-1.

Images

As with the variable pointer a, functionPointerInC will hold the address of the addition method as a value in runtime, as shown in Table 7-2.

Images

images Note: The address will vary when you execute Listing 7-1 in your environment.

Encapsulating Memory Handling with Delegates

From the above discussion and C code, you saw how you need to handle the memory addresses when using a function pointer. In .NET, C# introduces a new way to encapsulate all the memory handling of the method pointer in an object-oriented manner, which is called delegate. The delegates are conceptually similar to the function pointer in C/C++ as described earlier, but they are more easy to use and they provides the type safety.

Listing 7-2 provides an example that will help you understand delegate. The DelegateOfTheCalculator is declared as the type of delegate to store a method, which takes two inputs and returns a type of int. In the DelegateHandler method, an instance of the DelegateOfTheCalculator type has been instantiated and assigned the Add method, and in addition the Sub method was added and removed from the delegateOfTheCalculator.

Listing 7-2. Example of the Delegate in C#

using System;

namespace Ch07
{
class Program
{
/* declare a delegate type. */
delegate int DelegateOfTheCalculator(int a, int b);

static void Main(string[] args)
{
DelegateHandler();
}

static void DelegateHandler()
{
StandardCalculator standardCalculator = new StandardCalculator();

DelegateOfTheCalculator delegateOfTheCalculator =
new DelegateOfTheCalculator(standardCalculator.Add);
delegateOfTheCalculator += standardCalculator.Sub;
delegateOfTheCalculator -= standardCalculator.Sub;

/* Execute the Add method */
Console.WriteLine("Sum of a and b is:{0}", delegateOfTheCalculator(10, 10));
}
}

public class StandardCalculator
{
public int Add(int a, int b) { return a + b; }
public int Sub(int a, int b) { return a > b ? a - b : 0; }
public int Mul(int a, int b) { return a * b; }
}
}

This program will produce the following output:

Sum of a and b is:20

In Listing 7-2, you can see how delegate makes accessing the method easy in comparison to using the function pointer, as shown in Listing 7-1. You can also add multiple methods into one delegate type and remove multiple methods from the delegate without even handling any of the pointer functions. In the next section, you will explore more about the delegates in .NET.

Delegate in .NET

The Delegate and MulticastDelegate classes, defined in the mscorlib.dll assembly, as demonstrated in Figure 7-2, are responsible for taking care of the underlying function pointer in C#.

images

Figure 7-2. Delegate and MulticastDelegate class in the .NET

This MulticastDelegate class encapsulates the assigned methods inside it and provides functionality to execute those methods stored inside the MulticastDelegate class. The Delegate and MulticastDelegate classes’ declaration is shown in Listing 7-3.

Listing 7-3. The MulticastDelegate and Delegate Class Definition in IL

.class public abstract auto ansi serializable beforefieldinit MulticastDelegate
extends System.Delegate
{
/* Code removed */
.field private native int _invocationCount
.field private object _invocationList
}

.class public abstract auto ansi serializable beforefieldinit Delegate
extends System.Object
implements System.ICloneable, System.Runtime.Serialization.ISerializable
{
/* Code removed */
.field assembly object _methodBase
.field assembly native int _methodPtr { /* Code removed */ }
.field assembly native int _methodPtrAux { /* Code removed */ }
.field assembly object _target { /* Code removed */ }
}

Fields

Table 7-3 describes the important fields used in the Delegate and MulticastDelegate classes.

Images

Internal Work of the Delegate

While the C# compiler compiles the code as shown in Listing 7-2, it will do the following:

1. Convert the delegate declaration into a class, which inherits from the MulticastDelegate class, and MulticastDelegate class inherits from the Delegate class. As a result the C# compiler compiles delegate int DelegateOfTheCalculator(int a, int b) into a class that inherits from the MulticastDelegate class, as shown in Listing 7-4.

2. Instantiate the delegate class (generates for DelegateOfTheCalculator) and pass the initial method for which this delegate class has been instantiated. For Listing 7-2, the Add method is passed to the instance of the DelegateOfTheCalculator class as the initial method.

3. To add or remove a method from the delegate, CLR instantiates a new instance of the related delegate class and combines with the initial delegate instance to add a new method to the delegate object. As in Listing 7-2, the new instance of the DelegateOfTheCalculator will be instantiated and combined with the initial delegate instance as well as removing the stored delegate from the initial delegate instance.

Let’s find out more details about the delegate.

Decompiled Delegate Class

Listing 7-4 shows the decompiled IL code from Listing 7-2 using ildasm.exe.

Listing 7-4. Decompiled IL Code of Listing 7-2

.class auto ansi sealed nested private DelegateOfTheCalculator
extends [mscorlib]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname instance void
.ctor (object 'object', native int 'method') runtime managed /* Constructor */
{ /* code removed */ }

/* To asynchronously invoke the method stored into the delegate */
.method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult
BeginInvoke
(int32 a, int32 b, class [mscorlib]System.AsyncCallback callback, object 'object')
runtime managed
{ /* code removed */ }

/* This method will invoke when the delegate execution */
.method public hidebysig newslot virtual instance int32
EndInvoke
(class [mscorlib]System.IAsyncResult result) runtime managed
{ /* code removed */ }

/* To synchronously invoke the method stored into the delegate */
.method public hidebysig newslot virtual instance int32
Invoke
(int32 a, int32 b) runtime managed
{ /* code removed */ }
}

Instantiate, Combine, and Remove in Delegate

Let’s decompile the DelegateHandler from Listing 7-2 using the .NET Reflector tool, which shows delegate instantiation, and combine and remove multiple delegate instances for multiple methods, as shown in Listing 7-5.

Listing 7-5. DelegateHandler Method

.method private hidebysig static void DelegateHandler() cil managed
{
.maxstack 4
.locals init (
[0] class Ch07.StandardCalculator standardCalculator,
[1] class Ch07.Program/DelegateOfTheCalculator delegateOfTheCalculator)
L_0000: nop
L_0001: newobj instance void Ch07.StandardCalculator::.ctor()
L_0006: stloc.0

/* Loads the standardCalculator object (which has methods Add, Sub, Mul) onto the stack */
L_0007: ldloc.0

/* Loads the function pointer of the Add method from the standardCalculator onto the
* stack */
L_0008: ldftn instance int32 Ch07.StandardCalculator::Add(int32, int32)

/* CLR passes the standardCalculator object and the function pointer loaded in L_0008
* to the DelegateOfTheCalculator class which eventually call the constructor
* of the Delegate class.*/
L_000e: newobj instance void Ch07.Program/DelegateOfTheCalculator::.ctor(object, native int)
L_0013: stloc.1
L_0014: ldloc.1
L_0015: ldloc.0

/* Loads the function pointer of the Sub from standardCalculator onto the stack */
L_0016: ldftn instance int32 Ch07.StandardCalculator::Sub(int32, int32)

/* CLR passes the standardCalculator object and the function pointer loaded in L_0016
* to the DelegateOfTheCalculator class which eventually calls the constructor
* from the Delegate class.*/
L_001c: newobj instance void Ch07.Program/DelegateOfTheCalculator::.ctor(object, native int)

/* CLR passes delegate object instantiated in the L_001c and retrieved in L_0014
* to the Combine method of the Delegate class*/
L_0021: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::
Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
L_0026: castclass Ch07.Program/DelegateOfTheCalculator
L_002b: stloc.1
L_002c: ldloc.1

/* Loads the standardCalculator object into the stack */
L_002d: ldloc.0

/* Loads the function pointer of the Sub method onto the stack */
L_002e: ldftn instance int32 Ch07.StandardCalculator::Sub(int32, int32)

/* CLR passes the standardCalculator object and the function pointer loaded in L_002e
* to the DelegateOfTheCalculator class which calls the constructor
* of the Delegate class.*/
L_0034: newobj instance void Ch07.Program/DelegateOfTheCalculator::.ctor(object, native int)

/* CLR passes delegate object instantiated in the L_0034 and retrieved in L_002c
* to the Remove method of the Delegate class*/
L_0039: call class [mscorlib]System.Delegate [mscorlib]System.Delegate
::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.
Delegate)
L_003e: castclass Ch07.Program/DelegateOfTheCalculator
L_0043: stloc.1
L_0044: ldstr "Sum of a and b is:{0}"
L_0049: ldloc.1
L_004a: ldc.i4.s 10
L_004c: ldc.i4.s 10
L_004e: callvirt instance int32 Ch07.Program/DelegateOfTheCalculator::Invoke(int32, int32)
L_0053: box int32
L_0058: call void [mscorlib]System.Console::WriteLine(string, object)
L_005d: nop
L_005e: ret
}

The C# compiler uses the memory address to locate the method but wraps all these underneath the pointer of the method, the type it belongs to, and into the different fields defined in the delegate and MulticastDelegate classes.

Let’s find out more about this by analyzing the following:

· Instantiate: In the DelegateHandler method of Listing 7-5, in L_0008 the CLR loads the function pointer of the Add method onto the evaluation Stack and instantiates an instance of the DelegateOfTheCalculator in L_000e.

· Combine: In Listing 7-2, Add method has been added to the delegate using the + operator. The CLR will use the Combine method of the Delegate class internally to add a method into the delegate object. From Listing 7-5, in L_0016, the CLR loads the function pointer of Sub onto the evaluation Stack and instantiates an instance of the DelegateOfTheCalculator class in L_001c using the function pointer loaded in L_0016. In L_0021, the CLR calls the Combine method from the Delegate class to combine the original delegate object and the instance instantiated in the L_001c.

· Remove: In Listing 7-2, the Sub method has been removed from the delegate using the - operator. The CLR will use the Remove method of the Delegate class to remove the method stored in the delegate object. From Listing 7-5, in L_002e, the CLR loads the function pointer of the Submethod onto the evaluation Stack and instantiates an instance of the DelegateOfTheCalculator class in L_0034 using the function pointer loaded in L_002e. In L_0039, the CLR calls the Remove method of the Delegate class to remove this instance of the DelegateOfTheCalculator class instantiated in L_0034.

Images ldftn method_name: Pushes an unmanaged pointer (type native int) to a method referenced by method, on the Stack.

Examine the Memory

In .NET, delegate maintains the assigned or combined method information into fields such as _invocationList, _invocationCount, _methodPtr, _methodPtrAux, and _target. Let’s find out the initial information that CLR maintains in the delegate class while it initiates the DelegateOfTheCalculator class (using theAdd method as the initial method), as shown in Table 7-4.

Images

Table 7-4 shows that the CLR stored method Add of the StandardCalculator address information into _methodPtr field of delegate. CLR adds more methods into the delegate object, for example, if the instance of the DelegateOfTheCalculator class stores Add, Sub, and Mul methods from the StandardCalculatorclass, as shown in Listing 7-6.

Listing 7-6.Stores methods to the DelegateOfTheCalculator class

DelegateOfTheCalculator delegateOfTheCalculator = new DelegateOfTheCalculator(standardCalculat
or.Add);
delegateOfTheCalculator += standardCalculator.Sub;
delegateOfTheCalculator += standardCalculator.Mul;

In this case, the CLR will keep the method information in the delegate object, as shown in Table 7-5.

Images

In addition to the information shown in Table 7-5, CLR also keeps count of the number of methods added into the delegate, as shown in Table 7-6.

Images

To prove this, let’s get the address for the Add, Sub, and Mul methods from the StandardCalculator class by adding the following code block into the Main method of Listing 7-2.

StandardCalculator standardCalculator = new StandardCalculator();
var addressOfAddMethod = typeof(StandardCalculator).GetMethod("Add").MethodHandle.
GetFunctionPointer();
var addressOfSubMethod = typeof(StandardCalculator).GetMethod("Sub").MethodHandle.
GetFunctionPointer();
var addressOfMulMethod = typeof(StandardCalculator).GetMethod("Mul").MethodHandle.
GetFunctionPointer();

It will return the memory information, as shown in Table 7-7.

Images

From Table 7-7, you can see that .NET delegate is all about the method addresses and managing those addresses in an object-oriented way using different data structures defined in the Delegate and MulticastDelegate classes.

images Note: The memory address shown in Table 7-7 might be different when you run this locally.

Func and Action

The Func and Action delegates are a set of generic delegates that can work for methods of any return type (for Func) and reasonable number of arguments. These delegates are defined in the System namespace. The Action represents any function that may accept up to 16 parameters and returns void, for example, Action<T1>, where T1 refers to the input parameters and can be of any data type. Func is the same as Action but it has a return value of any type, for example, Func<T1, TResult> where T1 input parameters can be of any type and TResult is a returned value of any type. The only difference between Action and Func is the return value. In the following sections, we will explore more about Func and Action.

Func

The Func class is used to encapsulate method information in C#. The Func class is defined in the mscorlib.dll (C:\Windows\Microsoft.NET\ Framework\v4.0.30319), as shown in Figure 7-3, using the ildasm.exe.

images

Figure 7-3. Func in .NET

The signature of the Func<TResult> class is shown in Listing 7-7.

Listing 7-7. Signature of the Func<TResult>

.class public auto ansi sealed Func<+ TResult> extends System.MulticastDelegate

The Func class declaration is shown in Listing 7-8.

Listing 7-8. Func Class Definition in IL Format

.class public auto ansi sealed Func<+ TResult> extends System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname instance void
.ctor(object 'object', native int 'method') runtime managed
{}

.method public hidebysig newslot virtual instance class System.IAsyncResult
BeginInvoke(class System.AsyncCallback callback, object 'object') runtime managed
{}

.method public hidebysig newslot virtual instance !TResult
EndInvoke(class System.IAsyncResult result) runtime managed
{}

.method public hidebysig newslot virtual instance !TResult
Invoke() runtime managed
{}
}

Func<TResult> is a generic type, which inherits from the MulticastDelegate class and later inherits from the delegate class, as shown in Figure 7-4.

images

Figure 7-4. Func and Delegate relationship

The Func<TResult> class will have all the functionality and properties of the MulticastDelegate and Delegate types due to the inherent relationship between Func<TResult> and MulticastDelegate and the Delegate classes. The Func class has BeginInvoke, EndInvoke, and Invoke as well as the constructor method. The Funchas _methodPtr, _target, _methodPtrAux, and Method properties functions, as described in Table 7-3.

The Func class can take up to 16 inputs and returns per type. Listing 7-9 shows an overloaded Func.

Listing 7-9. Func Signature

Func<TResult>
Func<ISource,TResult>
Func<ISource1,ISource2,TResult>
Func<ISource1,ISource2,ISource3,TResult>
Func<ISource1,ISource2,ISource3,ISource4,TResult>
Func<ISource1,ISource2,ISource3,ISource4,ISource5,TResult>
Func<ISource1,ISource2,ISource3,ISource4,ISource5,ISource6,TResult>
Func<ISource1,ISource2,ISource3,ISource4,ISource5,ISource6,ISource7,TResult>
Func<ISource1,ISource2,ISource3,ISource4,ISource5,ISource6,ISource7,ISource8,TResult>
Func<ISource1,ISource2,ISource3,ISource4,ISource5,ISource6,ISource7,ISource8,...,ISource16,TResu
lt>

Internal Work of the Func

Let’s examine the example given in Listing 7-10 that is used to explain Func.

Listing 7-10. An Example of Func<TResult> Type

using System;

namespace Ch07
{
class Program
{
static void Main(string[] args)
{
ExampleOfFunc exampleOfFunc = new ExampleOfFunc();

Console.WriteLine("{0}", exampleOfFunc.Addition(exampleOfFunc.Add));
Console.WriteLine("{0}", exampleOfFunc.Addition(
() =>
{
return 100 + 100;
}));
}
}

public class ExampleOfFunc
{
public int Addition(Func<int> additionImplementor)
{
if (additionImplementor != null)
return additionImplementor();
return default(int);
}

public int Add()
{
return 1 + 1;
}
}
}

This program will produce the following output:

2
200

The C# compiler compiles the Func declaration as below:

· The CLR instantiates an instance of the Func<TResult> using the method passed to it.

· In the instantiation time, Func<TResult> calls the constructor of the MulticastDelegate, which initializes a set of variables using the method-related information (which is passed to it), such as method pointer, name, and so forth.

· The CLR passes this instance of Func back to the place, for example, as shown in Listing 7-10. Addition method accepts Func, and the CLR passes this newly instantiated Func<TResult> object to it.

· When the Func<TResult> object will be executed, the CLR will call the invoke method of that Func<TResult> object, which will execute the containing method inside the Func<TResult> object.

Figure 7-5 demonstrates the Func instantiation process.

images

Figure 7-5. Func instantiation process in CLR

Listing 7-11 provides the decompiled IL, using the .NET Reflector tool, from Listing 7-10 to explain the Func instantiation process described in Figure 7-5.

Listing 7-11. IL Code for the Func<TResult> Example

.class private auto ansi beforefieldinit Program extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{ /* Code removed */ }

.method private hidebysig static int32 <Main>b__0() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.
CompilerGeneratedAttribute::.ctor()
.maxstack 1
.locals init (
[0] int32 CS$1$0000)
L_0000: nop
L_0001: ldc.i4 200 /* 100 + 100 = 200*/
/* Code removed */
}

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 5
.locals init (
[0] class Ch07.ExampleOfFunc exampleOfFunc)
L_0000: nop
L_0001: newobj instance void Ch07.ExampleOfFunc::.ctor()
L_0006: stloc.0
L_0007: ldstr "{0}"

/* Load the instance of the ExampleOfFunc from the locals section at position 0. */
L_000c: ldloc.0
L_000d: ldloc.0

/* It loads the method pointer of the Add method on the stack*/
L_000e: ldftn instance int32 Ch07.ExampleOfFunc::Add()

/* Using the method pointer an instance of System.Func'1<int32> */
L_0014: newobj instance void [mscorlib]
System.Func'1<int32>::.ctor(object, native int)

/* Instance of the System.Func'1<int32> will be pass to the Addition method call */
L_0019: callvirt instance int32
Ch07.ExampleOfFunc::Addition(class [mscorlib]System.Func'1<int32>)
L_001e: box int32
L_0023: call void [mscorlib]System.Console::WriteLine(string, object)
L_0028: nop
L_0029: ldstr "{0}"
L_002e: ldloc.0

/* CLR load the CS$<>9__CachedAnonymousMethodDelegate1 field on to the stack */
L_002f: ldsfld class [mscorlib]
System.Func'1<int32> Ch07.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_0034: brtrue.s L_0049
L_0036: ldnull

/* It loads the method pointer of the <Main>b__0 anonymous method on the stack */
L_0037: ldftn int32 Ch07.Program::<Main>b__0()

/* Using the method pointer an instance of System.Func'1<int32> instantiated onto
* the Heap */
L_003d: newobj instance void
[mscorlib]System.Func'1<int32>::.ctor(object, native int)
/* CLR will store the instance of System.Func'1<int32> on the
* CS$<>9__CachedAnonymousMethodDelegate1 field */
L_0042: stsfld class [mscorlib]
System.Func'1<int32> Ch07.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_0047: br.s L_0049

/* CLR load the CS$<>9__CachedAnonymousMethodDelegate1 field on to the stack */
L_0049: ldsfld class [mscorlib]
System.Func'1<int32> Ch07.Program::CS$<>9__CachedAnonymousMethodDelegate1
/* CLR will call the Addition method by passing
* the CS$<>9__CachedAnonymousMethodDelegate1 field */
L_004e: callvirt instance int32
Ch07.ExampleOfFunc::Addition(class [mscorlib]System.Func'1<int32>)

L_0053: box int32
L_0058: call void [mscorlib]System.Console::WriteLine(string, object)
L_005d: nop
L_005e: ret
}

.field private static class [mscorlib]System.Func'1<int32> CS$<>9__
CachedAnonymousMethodDelegate1
{ /* Code removed */ }
}

Let’s analyze the IL code in Listing 7-11 to understand how the Func was handled by the CLR while executing the code in Listing 7-10.

Anonymous Method and Func

The C# compiler compiles the anonymous method from the Main method of the Program class as shown below:

() =>
{
return 100 + 100;
}

Into a method block <Main>b__0 and using this <Main>b__0, the CLR instantiates an instance of the Func'1<int32> type. To instantiate Func'1<int32>, CLR loads the function pointer for the <Main>b__0 using the ldftn IL instruction in L_0037 and, using the newobj IL instruction in L_003d, instantiates theFunc'1<int32>. This instance will later store into the field CS$<>9__CachedAnonymousMethodDelegate1 (type of Func'1<int32>) in L_0042, as shown below:

/* It loads the method pointer of the <Main>b__0 anonymous method on the stack */
L_0037: ldftn int32 Ch07.Program::<Main>b__0()

/* Using the method pointer an instance of System.Func'1<int32> instantiated onto the Heap*/
L_003d: newobj instance void
[mscorlib]System.Func'1<int32>::.ctor(object, native int)

/* CLR will store the instance of System.Func'1<int32> on the
* CS$<>9__CachedAnonymousMethodDelegate1 field */
L_0042: stsfld class [mscorlib]
System.Func'1<int32> Ch07.Program::CS$<>9__CachedAnonymousMethodDelegate1

In L_0049 the CLR loads the CS$<>9__CachedAnonymousMethodDelegate1 field and passes an argument to the Addition method in L_004e, and in the Addition method CLR will execute the <Main>b__0 method.

Instance Method and Func

In the Main method of Listing 7-11, in L_000e the CLR loads the function pointer of the Add method onto the evaluation stack and instantiates an instance of the Func'1<int32> in L_0014 and passes this instance of the Func'1<int32> to the Addition method, from where the Add method will be executed.

In Func, you can assign a method or an anonymous method as input to it, and that embedded method will be executed when the CLR executes the Func object. Let’s see another example of the Func that accepts five inputs and returns an output as Func<TSource1, TSource2, TSource3, TSource4, TSource5, TResult>, as shown in Listing 7-12.

Listing 7-12. Example of Func<TSource1,TSource2,TSource3,TSource4,TSource5, TResult>

using System;

namespace Ch07
{
class Program
{
static void Main(string[] args)
{
ExampleOfFunc exampleOfFunc = new ExampleOfFunc();

Console.WriteLine("{0}", exampleOfFunc.Addition(
exampleOfFunc.Add, /* Pass method name */
1, 2, 3, 4, 5));

Console.WriteLine("{0}", exampleOfFunc.Addition(
(a, b, c, d, e) =>
{
return a + b + c + d + e;
}, /* Pass anonymous method */
1, 2, 3, 4, 5));
}
}

public class ExampleOfFunc
{
public int Addition(
Func<int, int, int, int, int, int> additionImplementor,
int a,
int b,
int c,
int d,
int e)
{
if (additionImplementor != null)
return additionImplementor(a, b, c, d, e);

return default(int);
}

public int Add(int a, int b, int c, int d, int e)
{
return a + b + c + d + e;
}
}
}

This program will produce the following output:

15
15

The Func is a generic delegate that can be used to pass an argument of a method, which accepts an instance of the Func. The Func is defined in the .NET Framework so you do not need to worry about defining a generic delegate (as long as the number of input parameters in the Func support meets your requirement). If you need a generic delegate, which does not need to return any type other than void, the .NET Framework provides you with the Action delegate. Action is another generic delegate you can use in your program, and the next section will explore this further.

Action

The Action class is used to encapsulate the method information in C#. The Action class is defined in the mscorlib.dll (C:\Windows\Microsoft.NET\ Framework\v4.0.30319), as shown in Figure 7-6 using the ildasm.exe.

images

Figure 7-6. Action in .NET

The signature of the Action<T> class is shown in Listing 7-13.

Listing 7-13. Signature of the Action<T>

.class public auto ansi sealed Action<- T> extends System.MulticastDelegate

The Action class declaration is shown in Listing 7-14.

Listing 7-14. Action Class Definition in IL Format

.class public auto ansi sealed Action<- T1, - T2>
extends System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname instance void
.ctor(object 'object', native int 'method') runtime managed
{}

.method public hidebysig newslot virtual instance class System.IAsyncResult
BeginInvoke(!T1 arg1, !T2 arg2, class System.AsyncCallback callback, object 'object')
runtime managed
{}

.method public hidebysig newslot virtual instance void
EndInvoke(class System.IAsyncResult result) runtime managed
{}

.method public hidebysig newslot virtual instance void
Invoke(!T1 arg1, !T2 arg2) runtime managed
{}
}

Action is a generic type, which is inherited from the MulticastDelegate class, and it inherits from the delegate class, as shown in Figure 7-7.

images

Figure 7-7. Action and Delegate relationship

The Action class will have all the functionality and properties of the MulticastDelegate and Delegate types due to the inherent relationship between Action and the MulticastDelegate and Delegate classes. The Action class has BeginInvoke, EndInvoke, and Invoke and the constructor method. Action also has _methodPtr,_target, _methodPtrAux, and Method properties, which work as described in Table 7-3.

Action class can take up to 16 inputs, but it does not return anything. In .NET, Action has been implemented, as 17 overloaded Action with a different number of inputs, as shown in Listing 7-15.

Listing 7-15. Action Signature

Action
Action<T>
Action<T1, T2>
Action<T1, T2, T3>
Action<T1, T2, T3, T4>
Action<T1, T2, T3, T4, T5>
Action<T1, T2, T3, T4, T5, T6>
Action<T1, T2, T3, T4, T5, T6, T7>
Action<T1, T2, T3, T4, T5, T6, T7, T8>
Action<T1, T2, T3, T4, T5, T6, T7, T8, T9>
Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>

Internal Works of Action

Listing 7-16 provides an example to explain Action.

Listing 7-16. An Example of Action<T1,T2> Type

using System;
namespace Ch07
{
class Program
{
static void Main(string[] args)
{
ExampleOfAction exampleOfAction = new ExampleOfAction();
exampleOfAction.Addition(exampleOfAction.Add, 10, 10);
exampleOfAction.Addition(
(a, b) =>
{
Console.WriteLine("{0}", a + b);
}, 20, 20);
}
}

public class ExampleOfAction
{
public void Addition(Action<int, int> additionImplementor, int a, int b)
{
if (additionImplementor != null)
additionImplementor(a, b);
}

public void Add(int a, int b)
{
Console.WriteLine("{0}", a + b);
}
}
}

This program produces the following output:

20
40

The C# compiler compiles the Action declaration as below:

· The CLR instantiates an instance of the Action<T1, T2> using the input method to pass to it.

· In the instantiation time Action<T1, T2> calls the constructor of the MulticastDelegate, which initializes a set of variables based on the method-related information (which is passed to it), such as method pointer, name, and so forth.

· The CLR passes this instance of the Action back to the place, for example, in Listing 7-16 the Addition method accepts Action<int,int>, so the CLR passes this newly instantiated Action<T1, T2> object to it.

· When the Action<T1, T2> object will be executed, CLR will call the invoke method of that Action<T1, T2> object, which will execute the containing method inside the Action<T1, T2> object.

Figure 7-8 illustrates the Action instantiation process.

images

Figure 7-8. Action creation process in the CLR

Let’s decompile the IL code, as shown in Listing 7-17, using the .NET Reflector tool, for Listing 7-16 to understand the Action instantiation process demonstrated in Figure 7-8.

Listing 7-17. IL Code for the Action<T1,T2> Example

.class private auto ansi beforefieldinit Program extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{ /* Code removed */ }

.method private hidebysig static void <Main>b__0(int32 a, int32 b) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.
CompilerGeneratedAttribute::.ctor()
.maxstack 8
L_0000: nop
L_0001: ldstr "{0}"
L_0006: ldarg.0
L_0007: ldarg.1
L_0008: add
L_0009: box int32
L_000e: call void [mscorlib]System.Console::WriteLine(string, object)
L_0013: nop
L_0014: ret
}

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 4
.locals init (
[0] class Ch07.ExampleOfAction exampleOfAction)
L_0000: nop
L_0001: newobj instance void Ch07.ExampleOfAction::.ctor()
L_0006: stloc.0

/* It loads the instance of the ExampleOfAction
* from the locals section at position 0. */
L_0007: ldloc.0
L_0008: ldloc.0

/* It loads the method pointer of the Add method on the stack*/
L_0009: ldftn instance void Ch07.ExampleOfAction::Add(int32, int32)

/* Using the method pointer an instance of System.Action'2<int32, int32> */
L_000f: newobj instance void
[mscorlib]System.Action'2<int32, int32>::.ctor(object, native int)

L_0014: ldc.i4.s 10
L_0016: ldc.i4.s 10

/* Instance of the System.Action'2<int32, int32> will be passed
* to the Addition method call */
L_0018: callvirt instance void
Ch07.ExampleOfAction::Addition(class [mscorlib]System.
Action'2<int32, int32>, int32, int32)
L_001d: nop
L_001e: ldloc.0

/* CLR load the Ch07.Program::CS$<>9__CachedAnonymousMethodDelegate1
* field on to the stack */
L_001f: ldsfld class
[mscorlib]System.Action'2<int32, int32> Ch07.Program::
CS$<>9__CachedAnonymousMethodDelegate1
L_0024: brtrue.s L_0039
L_0026: ldnull

/* It loads the method pointer of the <Main>b__0 anonymous method on the stack */
L_0027: ldftn void Ch07.Program::<Main>b__0(int32, int32)

/* Using the method pointer loaded in L_0027, an instance
* of System.Action'2<int32, int32> will be instantiated*/
L_002d: newobj instance void
[mscorlib]System.Action'2<int32, int32>::.ctor(object, native int)

/* CLR will store the instance of System.Action'2<int32, int32> on the
* CS$<>9__CachedAnonymousMethodDelegate1 field */
L_0032: stsfld class
[mscorlib]System.Action'2<int32, int32> Ch07.Program::
CS$<>9__CachedAnonymousMethodDelegate1
L_0037: br.s L_0039

/* CLR load the CS$<>9__CachedAnonymousMethodDelegate1 field on to the stack */
L_0039: ldsfld class
[mscorlib]System.Action'2<int32, int32> Ch07.Program::
CS$<>9__CachedAnonymousMethodDelegate1
L_003e: ldc.i4.s 20
L_0040: ldc.i4.s 20

/* CLR will call the Addition method by passing
* the CS$<>9__CachedAnonymousMethodDelegate1 field */
L_0042: callvirt instance void
Ch07.ExampleOfAction::Addition(class
[mscorlib]System.Action'2<int32, int32>, int32, int32)
L_0047: nop
L_0048: ret
}

.field private static class
[mscorlib]System.Action'2<int32, int32> CS$<>9__CachedAnonymousMethodDelegate1
{ /* Code removed */ }
}

Let’s analyze the IL code in Listing 7-17 to understand the underlying method of how the Action was handled by the CLR while executing the code in Listing 7-16.

Anonymous Method and Action

The C# compiler compiles the anonymous method from the Main method of the Program class as shown below:

(a, b) =>
{
Console.WriteLine("{0}", a + b);
}

Into a method block <Main>b__0 and using this <Main>b__0, it instantiates an instance of the System.Action'2<int32, int32> type. To instantiate System.Action'2<int32, int32> CLR loads the function pointer of the <Main>b__0 using ldftn IL instruction in L_0027, and using the newobj IL instruction in L_002d it instantiates the System.Action'2<int32, int32> in Listing 7-17. This instance will later store into the field CS$<>9__CachedAnonymousMethodDelegate1 (type of System.Action'2<int32, int32>) in L_0032, as shown here:

/* It loads the method pointer of the <Main>b__0 anonymous method on the stack */
L_0027: ldftn void Ch07.Program::<Main>b__0(int32, int32)

/* Using the method pointer an instance of System.Action'2<int32, int32> */
L_002d: newobj instance void
[mscorlib]System.Action'2<int32, int32>::.ctor(object, native int)

/* CLR will store the instance of System.Action'2<int32, int32> on the
* CS$<>9__CachedAnonymousMethodDelegate1 field */
L_0032: stsfld class
[mscorlib]System.Action'2<int32, int32> Ch07.Program::CS$<>9__
CachedAnonymousMethodDelegate1

In L_0039, CLR loads CS$<>9__CachedAnonymousMethodDelegate1 and passes an argument to the Addition method in L_0042, and in the Addition method CLR will execute the <Main>b__0 method.

Instance Method and Action

In the Main method of Listing 7-17, in L_0009, the CLR loads the function pointer of the Add method onto the evaluation stack and instantiates an instance of the System.Action'2<int32, int32> in L_000f and passes it to the Addition from where the Add method will be executed.

So by using Action you can pass a method or an anonymous method as input to another method, such as the function pointer showed in Listing 7-1. The only difference with Func is that Func returns a value, whereas Action does not return any value.

Summary

In this chapter, we have learned about the delegate types in C#. First, we looked at the C# delegate in comparison to the function pointer in C, which shows you the basic similarity between the function pointer in C and delegate in .NET. We also explored how the C# compiler delegates pointer handling from developers. Finally, we explored the generic delegates Func and Action in .NET. You also learned how the C# compiler handles the Func and Action delegates. And the chapter concluded with a discussion about the use of the anonymous method in Func and Action. The next chapter will examine about the event in C#.