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

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

CHAPTER 3

images

Parameters

This chapter will discuss the parameters in C#. The focus will be to show different ways of passing parameters to a method, such as pass by value, pass by reference, and pass default value of the parameter. You will find explanations of these in detail and see how CLR handles these behind the scene.

Parameter

The parameter is the mechanism used to accept input to a method from the caller of that method. In a method signature, you can define the parameter or parameters to accept the input. These parameters could be any type, such as value types, for example, Int32, string, or reference type for an instance object, a person object of Person type, and so forth.

Figure 3-1 shows the parameters used in the method signature definition.

images

Figure 3-1. Parameter and arguments in C#

When you are accepting value from the caller of the method, the caller can pass just the value to the parameter or pass the reference of the variable and so on. Table 3-1 shows the different types of parameter accepting strategies you can use in a method declaration.

images

Method State Description Table

Method state describes the environment within which a method executes, and the method state description table is the temporary storage where CLR keeps information relating to a method while executing that method. Figure 3-2 shows different components of the method state table.

images

Figure 3-2. Method state description table

Let’s examine the details for the different components of the method state description table.

An Instruction Pointer

The instruction pointer (IP) is used to point to the next Common Intermediate Language (CIL) instruction to be executed by the Common Language Infrastructure (CLI) in the present method.

An Evaluation Stack

In .NET, the method in runtime contains an evaluation stack. The stack is empty upon method entry. The contents of the stack are local to the method and preserved across call instructions. The addresses in the evaluation stack are not addressable.

A Local Variable Array

An array of local variables will start at index 0. The values of local variables are preserved across calls (in the same sense as for the evaluation stack). A local variable can hold any data type. The address of an individual local variable can be taken using the ldloca instruction.

An Argument Array

The argument array will hold the values of the current method’s incoming arguments and will start at index 0. This argument array can be read and written by the logical index. The ldarga IL instruction can be used to take the address of an argument.

A Method Info Handle

The method info handle holds the signature of the method, the types of its local variables, and data about its exception handlers. This contains read-only information about the method.

A Local Memory Pool

The CLI includes instructions for dynamic allocation of objects from the local memory pool (localloc). Memory allocated in the local memory pool is addressable and is reclaimed upon method context termination.

A Return State Handle

The return state handle is used to restore the method state on return from the current method. Typically, this would be the state of the method’s caller.

A Security Descriptor

The security descriptor is not directly accessible to the managed code but is used by the CLI security system to record security overrides.

Value Type

This section will describe how the CLR takes care of the value type parameter during the method call.

Parameter by Value

Parameter by value is one of the common ways to pass the value type. The CLR will pass a copy of the value as an argument to the method. In Listing 3-1, you can see that in the BuiltInValuePassingAsValue class, MethodB accepts two parameters, a and b, of the built-in value type, such as int. MethodAcalls the MethodB method with int value 10 and 10 as an argument, as shown in Figure 3-3.

images

Figure 3-3. Parameter passing by value for the value type

When the program in Listing 3-1 executes, the CLR will call MethodB(a,b) from the MethodA. It will copy the value of a and b to the MethodB, so the MethodB will have the copy of the value a and b as demonstrated in Figure 3-3. Listing 3-1 presents where the built-in value is used to pass parameters.

Listing 3-1. An Example of the Built-In Value Type Parameter as a Value

using System;

namespace Ch03
{
class Program
{
static void Main(string[] args)
{
BuiltInValuePassingAsValue temp = new BuiltInValuePassingAsValue();
temp.MethodA();
}
}

public class BuiltInValuePassingAsValue
{
public BuiltInValuePassingAsValue()
{ Console.WriteLine("Built in value type passing as value"); }

public void MethodA()
{
int a = 10, b = 10;
MethodB(a, b);
Console.WriteLine("Method A: {0},{1}", a, b);
}

public void MethodB(int a, int b)
{ Console.WriteLine("Method B: {0},{1}", a, b); }
}
}

The program in Listing 3-1 will produce the following output:

Built in value type passing as value
Method B: 10,10
Method A: 10,10

Let’s explore more about the program in Listing 3-1 by examining the stack and heap while the program in Listing 3-1 is being executed.

images

Figure 3-4. Stack information while calling method using value type parameter as value

In Figure 3-4, you can see that while CLR calls the MethodB from the MethodA method it passes the value of the a(10=0xa) and b(10=0xa), and these values get stored in the LOCALS section of the MethodB method. As a result, any change of those values will not affect the value of the a and b in MethodA.

Let’s see the IL code generated using the .NET Reflector tool for the MethodA from Listing 3-1, as shows in Listing 3-2, to explore more about the parameter pass by value of the value type.

Listing 3-2. IL Code for the MethodA() of the BuiltInValuePassingAsValue

.method public hidebysig instance void MethodA() cil managed
{
.maxstack 3
.locals init (
[0] int32 a,
[1] int32 b)

L_0000: ldc.i4.s 10 /* The CLR push numeric constant 10 onto the stack */
L_0002: stloc.0 /* Pop the value 10 from stack into local variable
* at position 0.*/
L_0003: ldc.i4.s 10 /* The CLR push numeric constant 10 onto the stack */
L_0005: stloc.1 /* Pop the value 10 from stack into local variable
* at position 1.*/
L_0006: ldarg.0
L_0007: ldloc.0 /* Load local variable at position 0 from the Local
* section of the MethodA onto stack.*/
L_0008: ldloc.1 /* Load local variable at position 1 from the Local
* section of the MethodA onto stack.*/

L_0009: call instance void Ch03.BuiltInValuePassingAsValue::MethodB(int32, int32)
//Code removed
L_0024: ret
}

From Listing 3-2 you can see that, in L_0007 and L_0008, ldloc.0 and ldloc.1 IL instruction has been used that will load the local variable’s value from the Local variable section of the MethodA’s method state description table at location 0 and 1, which will be 10 and 10, onto the evaluation stack. Thecall instruction in the L_0009 passes those values from the evaluation stack to the method MethodB. The CLR copies those values to the argument array of the MethodB method state description table (as the incoming arguments’ array has been set by the CLR from the caller with the related values), and later in the MethodB, those values will be retrieved for the parameters a and b b y the CLR.

ldc.<type>: Load numeric constant

stloc: Pop value from stack to local variable

ldloc: Load local variable onto the stack

maxstack: Does not represent the size of the stack at runtime but is related to analysis of the program specially for the IL verification.

.locals init: Used to define a variable in the current method. The init keyword means the local variables will be initialized at runtime before the method executes.

To explore this more, see the generated IL code for the MethodB from Listing 3-1 as shown in Listing 3-3.

Listing 3-3. IL Code for the MethodB() of the BuiltInValuePassingAsValue

.method public hidebysig instance void MethodB(int32 a, int32 b) cil managed
{
.maxstack 8
L_0000: ldstr "Method B: {0},{1}"

L_0005: ldarg.1 /* ldarg.1 load the argument at position 1 onto the
* evaluation stack */
L_0006: box int32

L_000b: ldarg.2 /* ldarg.2 load the argument at position 2 onto the
* evaluation stack */
L_000c: box int32

/* Get the values from the evaluation stack and pass as argument of the WriteLine method */
L_0011: call void [mscorlib]System.Console::WriteLine(string, object, object)
L_0016: ret
}

From Listing 3-3, you can see that in L_0005 and L_000b, CLR loads the values from the argument array at positions 1 and 2, which will be 10 and 10, onto the evaluation stack. Those values will be used to pass an argument in L_0011 to call the WriteLine method of the Console class.

Parameter by ref

Let’s see how the built-in value type works while passing as a reference using the ref keyword. The program as shown in Listing 3-4 passes the built-in value type as a reference to a method, which accepts a built-in value type as the reference. When the CLR calls the MethodB(ref a,ref b) from the MethodA, it copies the address of the a and b to the MethodB. So the MethodB will have the address of a and b variables as demonstrated in Figure 3-5.

images

Figure 3-5. Parameter passing as ref

In the program in Listing 3-4, MethodB accepts two int-type parameters as references using the ref keyword.

Listing 3-4. Parameter Passing by ref for the Value Type

using System;

namespace Ch03
{
class Program
{
static void Main(string[] args)
{
BuiltInValuePassingAsRef temp = new BuiltInValuePassingAsRef();
temp.MethodA();
}
}

public class BuiltInValuePassingAsRef
{
public BuiltInValuePassingAsRef()
{
Console.WriteLine("Built in value type passing as ref");
}

public void MethodA()
{
int a = 10, b = 10;
MethodB(ref a, ref b);
Console.WriteLine("Method A: {0},{1}", a, b);
}

public void MethodB(ref int a, ref int b)
{
Console.WriteLine("Method B: {0},{1}", a, b);
a *= 2; b *= 2;
}
}
}

The program in Listing 3-4 produces the following output:

Built in value type passing as ref
Method B: 10,10
Method A: 20,20

To explore this more, see the generated IL code for the MethodA from Listing 3-4 and as shown in Listing 3-5.

Listing 3-5. IL Code for the MethodA of the BuiltInValuePassingAsRef

.method public hidebysig instance void MethodA() cil managed
{
.maxstack 3
.locals init (
[0] int32 a,
[1] int32 b)

L_0000: ldc.i4.s 10 /* The CLR push numeric constant 10 onto the
* stack */
L_0002: stloc.0 /* Pop the value 10 from stack into local variable
* at position 0.*/
L_0003: ldc.i4.s 10 /* The CLR push numeric constant 10 onto the
* stack */
L_0005: stloc.1 /* Pop the value 10 from stack into local variable
* at position 1.*/
L_0006: ldarg.0

L_0007: ldloca.s a /* Load the address of the a variable from the
* Local section of the method stack */

L_0009: ldloca.s b /* Load the address of the b variable from the
* Local section of the method stack */

/* Pass the address of the a and b as argument to the MethodB call */
L_000b: call instance void Ch03.BuiltInValuePassingAsRef::MethodB(int32&, int32&)

//Code removed
L_0026: ret
}

ldloca. <length>: Load local variable address. The ldloca instruction pushes the address of the local variable number index onto the stack, where local variables are numbered 0 upward.

In Listing 3-5, you can see the ldloca.s instruction used in L_0007 and L_0009, which loads the addresses of the local variables a and b onto the evaluation stack and are later used to call the MethodB method in L_000b. The CLR copies the addresses of a and b into the argument array of the method state description table of the MethodB, which are later used to retrieve the value of the a and b variables.

To explore this more, see the generated IL code for MethodB from Listing 3-4 as shown in Listing 3-6.

Listing 3-6. IL Code for the MethodB to Process Built-In Value Type ref Parameter

.method public hidebysig instance void MethodB(int32& a, int32& b) cil managed
{
.maxstack 8
L_0000: ldstr "Method B: {0},{1}"

/* ldarg.1 load the argument at position 1 onto the evaluation stack */
L_0005: ldarg.1

/* Get the address of the a from the top of the stack (loaded in L_0005) and
* using that address the CLR load the value of the variable located at
* that address.*/
L_0006: ldind.i4
L_0007: box int32
/* ldarg.2 load the argument at position 2 onto the evaluation stack */
L_000c: ldarg.2

/* Get the address from the top of the stack (loaded in L_000c) and using
* that address the CLR load the value of the variable located at that address.*/
L_000d: ldind.i4
L_000e: box int32

L_0013: call void [mscorlib]System.Console::WriteLine(string, object, object)

L_0018: ldarg.1
L_0019: dup
L_001a: ldind.i4
L_001b: ldc.i4.2
L_001c: mul

/* Store the value from the evaluation stack to the specified address */
L_001d: stind.i4

L_001e: ldarg.2
L_001f: dup
L_0020: ldind.i4
L_0021: ldc.i4.2
L_0022: mul
L_0023: stind.i4
L_0024: ret
}

ldind.<type>: Loads value indirectly onto the stack. It indirectly loads a value from address addr onto the stack.

stind.<type>: Stores the value indirect from the stack. The stind instruction stores value val at address addr.

In Listing 3-6, the IL instruction ldarg.1 is used in the L_0005 to load the first argument value (i.e., the address of variable a of the MethodA method) from the argument array of the method state description table of the MethodB method onto the evaluation stack. The next instruction ldind.i4 in L_0006will load a value from the address (which just pushed onto the stack using IL instruction in L_0005). The same technique is used in L_000c to L_000e to load the variable b of MethodA. These values are later used to write output in L_0013.

As you know, if you change the contents of variable a or b from this method, this will update the contents of variable a and b, which can be seen from the MethodA. In the L_0018 to L_001c, the contents of the variable a of the MethodA have been modified, later using stind.i4 in the L_001d, which will store the new updated value into the relevant address. The same techniques are used to update the value of variable b of MethodA in the L_001e to L_0024.

Let’s explore this more by examining the stack and heap while we are executing the program in Listing 3-4.

images

Figure 3-6. Value type pass as reference

In Figure 3-6, you can see that while CLR calls the MethodB from the MethodA method, it passes the addresses of the variables a(0x0027ecfc) and b(0x0027ecf8), and these addresses get stored in the PARAMETERS section of the MethodB method. As a result, any change of those values affects the original value of the a and b in MethodA.

Reference Type

This section will explain how the CLR takes care of the reference type parameter during the method call.

Parameter by Value

This section describes how CLR deals with the reference type when it passes a value. In Listing 3-7, in the ObjectAsValue class, MethodB is accepting a parameter of Person type from the MethodA, the MethodB is called with an instance of the Person type as an argument, as shown in Figure 3-7.

images

Figure 3-7. Parameter passing with object as value type

When the CLR calls the MethodB(Person aPerson) from MethodA, it will copy the aPerson object to the argument array of the method state description table of the MethodB so the MethodB will have the aPerson object, as shown in Listing 3-7.

Listing 3-7. Object Passing as Value in Parameter Passing

using System;

namespace Ch03
{
class Program
{
static void Main(string[] args)
{
ObjectAsValue temp = new ObjectAsValue();
temp.MethodA();
}
}

public class ObjectAsValue
{
public ObjectAsValue()
{
Console.WriteLine("Object as value");
}
public void MethodA()
{
Person aPerson = new Person()
{
Name = "APerson"
};
MethodB(aPerson);
Console.WriteLine("Method A: {0}", aPerson.Name);
}
public void MethodB(Person aPerson)
{
Console.WriteLine("Method B: {0}", aPerson.Name);
aPerson.Name = "Updated" + aPerson.Name;
}
}

public class Person
{
public string Name
{ get; set; }
}
}

The program in Listing 3-7 produces the following output:

Object as value
Method B: APerson
Method A: UpdatedAPerson

To explore this more, let’s see the generated IL code for MethodA from Listing 3-7 as shown in Listing 3-8.

Listing 3-8. IL Code of the Program in Listing 3-7

.method public hidebysig instance void MethodA() cil managed
{
.maxstack 2
.locals init (
[0] class Ch03.Person aPerson,
[1] class Ch03.Person <>g__initLocal0)

L_0000: newobj instance void Ch03.Person::.ctor()
L_0005: stloc.1
L_0006: ldloc.1
L_0007: ldstr "APerson"
L_000c: callvirt instance void Ch03.Person::set_Name(string)
L_0011: ldloc.1
L_0012: stloc.0

L_0013: ldarg.0
L_0014: ldloc.0
L_0015: call instance void Ch03.ObjectAsValue::MethodB(class Ch03.Person)

L_001a: ldstr "Method A: {0}"
L_001f: ldloc.0
L_0020: callvirt instance string Ch03.Person::get_Name()
L_0025: call void [mscorlib]System.Console::WriteLine(string, object)
L_002a: ret
}

In this IL code, two local variables have been stored at positions 0 and 1, such as aPerson and <>g__initLocal0, which is an instance of the Person type. In L_0000 to L_0011, an instance of the Person type will be created and the CLR will load that instance to the local variable at position 1, which will later store it at position 0, which is the aPerson object.

In L_0013, the IL code ldarg.0 will load the argument value from the position 0 and in L_0014 the IL code ldloc.0 will load the current local variable at position 0, which will be used as the argument of the method call for MethodB in the L_0015. Therefore, you can see that this is passed as a value to the method call MethodB. Figure 3-8 shows the stack–heap relationship in the memory while executing the code in Listing 3-7.

images

Figure 3-8. Stack and heap information while passing the parameter of an object as a value

You can see the IL code in Listing 3-9 for the MethodB, and you can see that in L_0005, ldarg.1 is used to load the argument value of aPerson and later on to call the get_Name() method from the aPerson object passed from MethodA.

Listing 3-9. IL Code of MethodB

.method public hidebysig instance void MethodB(class Ch03.Person aPerson) cil managed
{
.maxstack 8
L_0000: ldstr "Method B: {0}"

L_0005: ldarg.1
L_0006: callvirt instance string Ch03.Person::get_Name()

L_000b: call void [mscorlib]System.Console::WriteLine(string, object)

L_0010: ldarg.1
L_0011: ldstr "Updated"
L_0016: ldarg.1
L_0017: callvirt instance string Ch03.Person::get_Name()
L_001c: call string [mscorlib]System.String::Concat(string, string)

L_0021: callvirt instance void Ch03.Person::set_Name(string)

L_0026: ret
}

In L_0021, the set_Name(string) method of the Person class has been called to update the value of the Name property, and get_Name() and set_Name(string) are the internal methods for the property Name of the aPerson object. When you pass an object of reference type as an argument of method call (which accepts an object of the related type), you can modify the value of the public property of that object. But if you try to update the object itself (i.e., replace the existing contents of the aPerson object with the new instance of the Person object), as demonstrated in Listing 3-10, it will not be visible inMethodA.

Listing 3-10. Updated Code in MethodB

public void MethodB(Person aPerson)
{
Console.WriteLine("Method B: {0}", aPerson.Name);
aPerson = new Person()
{
Name = "New name"
};
}

To explore this more, see the generated IL code for MethodB from Listing 3-10 as shown in Listing 3-11.

Listing 3-11. IL Code for Listing 3-10.

.method public hidebysig instance void MethodB(class Ch03.Person aPerson) cil managed
{
.maxstack 2
.locals init (
[0] class Ch03.Person <>g__initLocal1)
L_0000: ldstr "Method B: {0}"
L_0005: ldarg.1
L_0006: callvirt instance string Ch03.Person::get_Name()
L_000b: call void [mscorlib]System.Console::WriteLine(string, object)
L_0010: newobj instance void Ch03.Person::.ctor()

L_0015: stloc.0
L_0016: ldloc.0
L_0017: ldstr "New name"
L_001c: callvirt instance void Ch03.Person::set_Name(string)

L_0021: ldloc.0

L_0022: starg.s aPerson

L_0024: ret
}

The IL instruction ldloc.0 in L_0021 will load the local variable at position 0, which is <>g__initLocal0, onto the evaluation stack and, using the starg.s aPerson instruction, CLR will load this new <>g_initLocal0 object into the argument, which holds the aPerson object. However, this new value will never come across to MethodA. Therefore, the contents of the original object will never be replaced, but to do this you will need to pass the reference of the object to the method call, as discuss in the next section.

starg.s: The starg instruction pops a value from the stack and places it in the argument slot at a specific position.

Parameter by ref

The reference of a type passes as a reference to the method call. In ObjectAsValue class, MethodB is accepting a parameter of Person type as a reference, and the MethodB is being called from MethodA with an instance of the Person type by passing the address of that Person instance as an argument, as shown inFigure 3-9.

images

Figure 3-9. Reference type used as ref

When the CLR executes the MethodB(ref Person aPerson) from the MethodA, it will copy the address of the aPerson object to the argument array of the method state description table of the MethodB, so the MethodB will have the address of aPerson object, as shown in Listing 3-12.

Listing 3-12. Passing Object Type as ref

using System;

namespace Ch03
{
class Program
{
static void Main(string[] args)
{
ObjectAsValue temp = new ObjectAsValue();
temp.MethodA();
}
}

public class ObjectAsValue
{
public ObjectAsValue()
{
Console.WriteLine("Object as value");
}

public void MethodA()
{
Person aPerson = new Person()
{
Name = "APerson"
};
MethodB(ref aPerson);
Console.WriteLine("Method A: {0}", aPerson.Name);
}

public void MethodB(ref Person aPerson)
{
Console.WriteLine("Method B: {0}", aPerson.Name);
aPerson = new Person()
{
Name = "New name"
};
}
}

public class Person
{
public string Name
{ get; set; }
}

}

The program in Listing 3-12 produces the following output:

Object as value
Method B: APerson
Method A: New name

To explore this more, see the generated IL code for MethodA from Listing 3-12 as shown in Listing 3-13.

Listing 3-13. IL Code of the MethodA Method

.method public hidebysig instance void MethodA() cil managed
{
.maxstack 2
.locals init (
[0] class Ch03.Person aPerson,
[1] class Ch03.Person <>g__initLocal0)

L_0000: newobj instance void Ch03.Person::.ctor()
L_0005: stloc.1
L_0006: ldloc.1
L_0007: ldstr "APerson"
L_000c: callvirt instance void Ch03.Person::set_Name(string)
L_0011: ldloc.1
L_0012: stloc.0

L_0013: ldarg.0

/* Load the address of the local variable aPerson onto the Stack. */
L_0014: ldloca.s aPerson

/* The CLR will use the address of the aPerson object from the Stack and
* pass as argument of the MethodB call.*/
L_0016: call instance void Ch03.ObjectAsValue::MethodB(class Ch03.Person&)

L_001b: ldstr "Method A: {0}"
L_0020: ldloc.0
L_0021: callvirt instance string Ch03.Person::get_Name()
L_0026: call void [mscorlib]System.Console::WriteLine(string, object)
L_002b: ret
}

The CLR instantiates an instance of the Person using newobj instruction in L_000. The ldloca.s aPerson instruction in the L_0014 label will push the address of the local variable aPerson onto the stack. This address will be copied to the argument array of the method state description table of the MethodBwhile initializing the MethodB call, MethodB(class Ch03.Person&) in L_0016. The MethodB, on the other hand, is accepting an address of the Person type object. When you update the aPerson object with the new instance of the Person, this change will be visible from the MethodA method, as shown in Listing 3-14.

Listing 3-14. IL Code of the MethodB Method

.method public hidebysig instance void MethodB(class Ch03.Person& aPerson) cil managed
{
.maxstack 3
.locals init (
[0] class Ch03.Person <>g__initLocal1)
L_0000: ldstr "Method B: {0}"

/* Load the address passed via the aPerson parameter onto the stack */
L_0005: ldarg.1

/* Load the contents of the aPerson object indirectly */
L_0006: ldind.ref

L_0007: callvirt instance string Ch03.Person::get_Name()
L_000c: call void [mscorlib]System.Console::WriteLine(string, object)

L_0011: ldarg.1
L_0012: newobj instance void Ch03.Person::.ctor()
L_0017: stloc.0

L_0018: ldloc.0

L_0019: ldstr "New name"
L_001e: callvirt instance void Ch03.Person::set_Name(string)

/* Load the value of the local variable at position at 0 on the stack */
L_0023: ldloc.0

/* It will store the object from the stack on to the memory object which actually
* replace the original aPerson passed as argument. */
L_0024: stind.ref

L_0025: ret
}

The IL instruction ldind.ref in L_0006 will load the object onto the stack. The IL instruction ldloc.0 in the L_0018 will load the local object aPerson stored into the position at 0 and set the new value for the Name field of the aPerson object. This updated aPerson will be stored in the address of the aPersonobject of the MethodA using the stind.ref in L_0024. This stind.ref instruction will store the new object instance at the given address as passed by the caller. The address passed from the MethodA to the MethodB, stind.ref will store this new instance of Person object from the evaluation stack to that location. As a result, this updated Person object will be visible from the MethodA.

Default Parameter Value

Default parameter value is a way to declare an optional parameter in a method declaration. In other words, when you define a method with a parameter, you can also set the default value for the parameter. Then the caller of the method does not have to pass the value for the parameter while calling the method.

When CLR handles any method that has a parameter with the default value set, it treats these methods in two ways:

· If you do not set any value for the parameters when you call the method, then CLR grabs the default values from that method signature and passes those values as arguments for that method call.

· On the other hand, if you set the value for the parameters from the calling method, then CLR will take these values as arguments to call that method.

An example is given in Listing 3-15, where the default value has been set to the method parameter and that method has been called twice, with and without passing a parameter value.

Listing 3-15. An Example of Default Value for the Parameter of the Value Type

using System;
namespace Ch03
{
class Program
{
static void Main(string[] args)
{
int result = GetTotalPrice(); /* Test scenario 1- with the value */
result = GetTotalPrice(55); /* Test scenario 2 - with the value*/
}
public static int GetTotalPrice(int basePrice = 40)
{
return basePrice - (basePrice * 10) / 100;
}
}
}

The program in Listing 3-15 will produce approximately the following stack information, as demonstrates in Figure 3-10, while executing.

images

Figure 3-10. Executing GetTotalPrice method without passing value for the parameters

As you can see, the PARAMETERS section of the GetTotalPrice method’s stack basePrice has been initialized with the default value 0x00000028(40). While CLR moves the program control into the GetTotalPrice, it will use the value of the basePrice variable stored in the PARAMETERS section of the stack, and after finishing the processing it will return the result 0x00000024(36) of the expression basePrice (basePrice * 10 )/100.

To understand more in depth about the default value parameter, let’s look at the decompiled IL code for Listing 3-15 as shown in Listing 3-16.

Listing 3-16. IL Code for Listing 3-15

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

.method public hidebysig static int32 GetTotalPrice([opt] int32 basePrice) cil managed
{
.param [1] = int32(40)
.maxstack 3
.locals init (
[0] int32 CS$1$0000)
L_0000: nop

/* ldarg will load the argument at position 0 from the arguments array of this method's
* method state description table. These arguments have been passed from the
* caller which is Main method and the argument at position 0 is 40. */
L_0001: ldarg.0
L_0002: ldarg.0
L_0003: ldc.i4.s 10
L_0005: mul
L_0006: ldc.i4.s 100
L_0008: div
L_0009: sub
L_000a: stloc.0
L_000b: br.s L_000d
L_000d: ldloc.0
L_000e: ret
}

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 1
.locals init (
[0] int32 result)
L_0000: nop

/* Push numeric constant 40 onto the evaluation stack. The C# compiler will take the
*constant value 40 from the signature of the method GetTotalPrice */
L_0001: ldc.i4.s 40

/* Get the top value from the evaluation stack, use as the parameter value and
* call the GetTotalPrice method */
L_0003: call int32 Ch03.Program::GetTotalPrice(int32)

/* Store the result return from the IL code L_0003. */
L_0008: stloc.0

/* Push numeric constant 0x37(55) onto the evaluation stack. */
L_0009: ldc.i4.s 0x37

/* Get the top value from the evaluation stack, use as the parameter value and
* call the GetTotalPrice method */
L_000b: call int32 Ch03.Program::GetTotalPrice(int32)

/* Store the result return from the IL code L_0003. */
L_0010: stloc.0
L_0011: ret
}
}

The IL code in Listing 3-16 demonstrates that:

· When the CLR Jits the Main method, it will get the default value set for the GetTotalPrice method and embed that numeric constant into the IL instruction in L_0001 of the Main method, as shown in Listing 3-16.

· The CLR will use this numeric constant 40 in the Main method to call the GetTotalPrice.

· From the GetTotalPrice method, CLR will access the arguments from the method state description table of the GetTotalPrice method and get the value for the parameter basePrice. The value of the basePrice has been passed to the GetTotalPrice method by the CLR in runtime as GetTotalPrice has been called without any argument value for the parameter basePrice.

ldc.<type>: It loads a numeric constant on the stack.

ldc.i4.s N: It pushes N onto the stack as int32.

Figure 3-11 shows that GetTotalPrice is executing using the argument value 55 (0x37). While the CLR executes as in Listing 3-15, it will use the parameter value for the basePrice, which is 55 (0x37), instead of the default value 40.

images

Figure 3-11. CLR executing the GetTotalPrice(55)

In C#, the reference type can be set as the default value for the parameter using the default keyword or setting null as the default value. You can try to set the default value for the reference type, as shown in Listing 3-17.

Listing 3-17. Reference Type as the Default Value of a Parameter

using System;

namespace Ch03
{
class Program
{
static void Main(string[] args) { }

/* The C# compiler complain in here as reference type Person used for the default
value.*/
public static string GetPersonDetails(Person aPerson = new Person())
{ return aPerson.ToString(); }
}

public class Person { }
}

In the compile time, the C# compiler will throw an exception as shown:

Error 11 Default parameter value for 'aPerspon' must be a compile-time constant
J:\Book\ExpertC#2012\SourceCode\BookExamples\Ch03\Program.cs 10 65 Ch03

However, if you define the GetPersonDetails method, as below, you will be able to set the default value for the reference type:

public static string GetPersonDetails(Person aPerspon = null)
{ return aPerspon.ToString(); }

Or it is defined as:

public static string GetPersonDetails(Person aPerspon = default(Person))
{ return aPerspon.ToString(); }

You can use string as the default value of a parameter, as shown in Listing 3-18.

Listing 3-18. An Example of Named Parameter in C#

using System;

namespace Ch03
{
class Program
{
static void Main(string[] args)
{
GetNameWithDefaultValue();
GetNameWithDefaultValue("Expert C# 5.0 by Mohammad Rahman", "C#");
}

/* Default value has been set as of string type */
public static void GetNameWithDefaultValue(
string name = "Expert C# 5.0: with the .NET 4.5 Framework",
string language = ": C#")
{
Console.WriteLine("{0} {1}", name, language);
}
}
}

In the above code, the GetNameWithDefaultValue method defined with the two parameters name and language of string type with its default value "Expert C# 5.0: with the .NET 4.5 Framework" and ": C#". The caller of this method, for example, Main method, does not have to pass any value for the name and addressparameters or they could be. To explore this more, see the generated IL code for Listing 3-18 as shown in Listing 3-19.

Listing 3-19. IL Code for GetNameWithDefaultValue Method

.class private auto ansi beforefieldinit Program extends [mscorlib]System.Object
{
/* Code removed */
.method public hidebysig static void
GetNameWithDefaultValue([opt] string name, [opt] string language) cil managed
{
.param [1] = string('Expert C# 5.0: with the .NET 4.5 Framework')
.param [2] = string(': C#')
.maxstack 8
L_0000: nop
L_0001: ldstr "{0} {1}"

/* Load the argument value at position 0 and 1 from the argument values
* of the Method state description table into the evaluation stack and
* execute the following IL instruction using those values from the
* evaluation stack.*/
L_0006: ldarg.0 /* refers to the name */
L_0007: ldarg.1 /* refers to the language */

L_0008: call void [mscorlib]System.Console::WriteLine(string, object, object)
L_000d: nop
L_000e: ret
}

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
L_0000: nop

/* In the compile time the C# compiler extract the default values set for the parameter
* name and language from the GetNameWithDefaultValue method and
* embed into the IL instruction L_0001 and L_0006.
* The CLR load the given string into the heap as result those string will have
* memory address on the heap. The CLR use memory addresses as the parameter
* value for the GetNameWithDefaultValue method call.*/
L_0001: ldstr "Expert C# 5.0: with the .NET 4.5 Framework"
L_0006: ldstr ": C#"

L_000b: call void Ch03.Program::GetNameWithDefaultValue(string, string)
L_0010: nop

/* The CLR load the given string into the heap as result those string will have
* memory address on the heap. The CLR use memory addresses as the parameter
* value for the GetNameWithDefaultValue method call.*/
L_0011: ldstr "Expert C# 5.0 by Mohammad Rahman"
L_0016: ldstr "C#"

L_001b: call void Ch03.Program::GetNameWithDefaultValue(string, string)
L_0020: nop
L_0021: ret
}
}

You can see in L_0001 and L_0006 that CLR loads the "Expert C# 5.0: with the .NET 4.5 Framework" and ":C#" from the parameter’s array of the GetNameWithDefaultValue method to the Main method using the ldstr instruction. This will be used as an argument to call the GetNameWithDefaultValue method, as you can see in the IL instruction from L_0001 to L_000b of the Main method. On the other hand, for the L_0011 and L_0016, CLR does not load a parameter value from the GetNameWithDefaultValue method while executing GetNameWithDefaultValue("Expert C# 5.0 by Mohammad Rahman", "C#") from the Main method. Figure 3-12shows stack and heap information that is formed while executing the GetNameWithDefaultValue method without any default values.

images

Figure 3-12. CLR Executes GetNameWithDefaultValue();

From Figure 3-12 you can see that all the values of the related string have been stored into the heap, and CLR just used the memory reference to access those values. This exemplifies executing the GetNameWithDefaultValue method while no value for the parameter has been set. However, inFigure 3-13, you can see that CLR handles the GetNameWithDefaultValue method call the same way, but this time the argument value has been set to the GetNameWithDefaultValue method instead of CLR getting values from the GetNameWithDefaultValue method signature.

images

Figure 3-13. CLR Executes GetNameWithDefaultValue(“Expert C# 5.0 by Mohammad Rahman”, “C#”);

Summary

This chapter presented information about parameters, including value type and reference type. The CLR has some special mechanisms when you use the ref and out keywords for value types and reference types, which is seen in the debugging information produced by the windbg.exe tool. In the next chapter, we will explore the methods used in C#.