Arguments and Parameterized Methods


Implementing useful methods that don't accept arguments can be quite limiting. Most methods will process data in some way; therefore the data (or a reference pointer to the data) should be passed to the method. Variables passed to methods are usually referred to as arguments and the values that refer to those arguments within the method are called parameters. Methods that accept arguments are therefore called parameterized methods.

Parameter Types

Passing arguments to parameterized methods is not a trivial consideration. Deliberation must be given to the type of data being passed, how the data will be processed within the method, and how the processing (if any) should be passed back or made visible to the client code. We also need to consider if the argument must be initialized before it is passed and also consider how many arguments the method will require and if the number of arguments is arbitrary.

When an argument is passed to a method, we are initially given two main choices over how the argument is passed. We can pass the actual value of the argument (passing by value) or we can pass a reference (passing by reference) to the argument. Passing an argument by value is the default in C# and is the same in all languages that target the .NET Framework.

Passing by Value

Passing an argument by value involves making a physical copy of the data from the client code (the argument) into the method (the parameter). Both the client code and the method then have variables holding their own separate copy of the data. At the time the method is invoked, this data is exactly the same. However, the method will then proceed with its sequence of statements and potentially change the value of this data locally within the method itself. Changes to the value of the parameter do not affect the value of the argument, which is separate.

Passing an argument by value requires no additional keywords within the method declaration. The following example called passing_by_val.cs demonstrates passing data by value:

    using System;    namespace parameter_types    {      public class Calculator      {        public static decimal CalculateInterest(decimal balance)        {          decimal interest = (decimal)45.11;          balance = balance + interest;          return (balance);        }      }      class BatchRun      {        static void Main()        {          decimal balance = (decimal)4566.54;          Console.WriteLine("Balance = " + balance);          Console.WriteLine("Balance + Interest = " +              Calculator.CalculateInterest(balance));          Console.WriteLine("Balance = " + balance);        }      }    } 

This example calculates the interest on the balance of a bank account. The simple CalculateInterest() method takes a balance argument and adds the interest. The following output is produced:

    Balance = 4566.54    Balance + Interest = 4611.65    Balance = 4566.54 

Notice how the account balance has remained unchanged after calling CalculateInterest(). This is because the value of the argument has been copied to the method parameter. Both the argument and parameter hold separate values.

Passing by Reference

Passing arguments by reference involves copying a reference to the data instead of the data itself. We think of this as if we are passing a pointer to the location in memory where the data is to the method. Both the client code and the method hold separate references to the same data. Because of this, changes made to the parameter by the method are visible to the client code. The changes a method makes to a parameter that can affect the client code are called side effects. When an argument is passed by reference, think of the parameter as an alias to the argument.

What actually happens is that the value is copied in to the method, just as it is when passed by value; when the method terminates, the value is copied back out again into the location from which it came, along with any changes that have been made to the value in the course of the method executing.

Passing an argument by reference requires the use of the additional ref keyword in both the method declaration, and the calling client code. Failure to use the ref keyword in the calling program results in a compile-time error:

 Argument 'n': cannot convert from '<type>' to 'ref <type>'. 

The ref keyword overrides the default pass by value behavior. The following example demonstrates passing data by reference:

    using System;    namespace parameter_types    {      public class Calculator      {        public static decimal CalculateInterest(ref decimal balance)        {          decimal interest = (decimal)45.11;          balance = balance + interest;          return (balance);        }      }      class BatchRun      {        static void Main()        {          decimal balance = (decimal)4566.54;          Console.WriteLine("Balance = " + balance);          Console.WriteLine("Balance + Interest = " +              Calculator.CalculateInterest(ref balance));          Console.WriteLine("Balance = " + balance);        }      }    } 

The CalculateInterest() method has been modified to accept the argument by reference by using the ref keyword. The calling client code has also been modified to pass the arguments by reference using the ref keyword. This means that the parameter and argument hold a reference to the same value.

Important

Arguments passed by reference using the ref keyword must be initialized before being passed to the method. Failure to initialize an argument results in the following compile time error: Use of unassigned local variable <variable name>.

When running the program, the following should be displayed –

    Balance = 4566.54    Balance + Interest = 4611.65    Balance = 4611.65 

Notice how the account balance has been changed as a side effect of calling the CalculateInterest() method. In this example, the CalculateInterest() method also returns the new account balance. This is redundant because the account balance is changed automatically by virtue of the fact that it was passed by reference. Thus we have two options:

  • Pass arguments by value and get a return value back from the method

  • Pass arguments by reference, declare the method as void, and do not return a value

The most important factor influencing our choice here is this: if you need a method to return several values, passing by reference can simulate this. A method can only return one value; passing by reference can be used to produce side effects on all arguments passed.

If you are considering passing arguments by reference to alter several arguments, read on to the next section, which discusses output parameters. Output parameters can sometimes provide a more elegant and appropriate solution to this problem.

Output Parameters

Output parameters solve the problem of getting a method to simulate the manipulation and passing back of more than one value. Similar to ref parameters, output parameters are also passed by reference and the out parameter also behaves like an alias to the argument, but there is one subtle and important difference to ref arguments, and that is that:

Important

Arguments passed by reference using the out keyword do not have to be initialized before being passed to the method.

By implication, if an argument has no value (or if it is a null reference), then the method cannot inadvertently do any harm to the argument. Output parameters are also considered unassigned within a method; methods with out parameters must therefore initialize all out parameters before the method returns.

The following example parameter_types.cs demonstrates passing data by reference using out parameters:

    using System;    public class Calculator    {      public static decimal CalculateInterest(out decimal balance)      {        decimal interest = (decimal)45.11;        balance = (decimal)4566.54;        balance += interest;        return (balance);      }    }    class BatchRun    {      static void Main()      {        decimal balance;        Console.WriteLine("Balance + Interest = " +            Calculator.CalculateInterest(out balance));      }    } 

When you run this program, the following should be displayed in the console window:

    C:\Class Design\Ch 03>parameter_types    Balance + Interest = 4611.65 

Looking at the program, here are the main points to note:

  • The account balance (balance) is uninitialized before it is passed to CalculateInterest().

  • Output parameters are always assumed to be uninitialized; CalculateInterest() must initialize the balance parameter before returning.

  • If output parameters have already been initialized before being passed to the method, the method must ignore this and assign its own value anyway. In this example, assigning the balance first and then attempting the interest addition operation will cause a compile-time error because balance must be initialized in the method before it returns.

  • Both the calling program and the method declaration explicitly use the out keyword, in the same manner as ref parameters.

Note

Output parameters are currently unique to C# within the .NET Framework. Visual Basic .NET does not support the concept of output parameters. C# output parameters are treated the same as regular by-reference parameters when consumed by other .NET programming languages. Therefore, both C# out parameters and C# ref parameters behave like Visual Basic .NET ByRef parameters.

Output parameters are very useful for methods that need to simulate returning several values. Because out parameters are treated as uninitialized, the method must assign a value to all out parameters before returning. This is similar in principle to methods that return a single value where the method must assign and return a single value of the correct type.

The C# compiler forces the use of the out keyword in both the client code and the method signature; this provides very clear indications to a developer as to the purpose of the parameter within the method.

Passing Reference Types versus Value Types

Choosing whether to pass arguments by reference (ref or out) or by value not only depends on how you want the method to behave in terms the method side effects, performance, and return values; but also on the type of data that you want to pass to the method.

.NET types are either value types or reference types. As discussed in Chapter 1, value types include primitive data types, structures, and enumerations. A simple assignment of a value type to another value type variable results in two variables containing separate data. In fact, all value type assignments, including passing arguments to method parameters by value, result in the copying of data to the new variable. The lifetime of a value type can be directly controlled by the method or client code because when a method returns all value types fall out of scope and their memory allocation is immediately released. When a value type variable goes out of scope, the variable and all of its data are removed from the stack, immediately freeing up memory.

Reference types can be of any object type (including arrays and strings), and they are heap based. Reference type variables contain references. It is these references that are the values that are passed by value or by reference in calling methods.

A reference type can have many references pointing at it. This happens when more than one stack-based variable points to the same heap-based reference type. Reference types will not go out of scope on the heap (and consequently be made available for garbage-collection) until all references are removed from the stack. The garbage collector governs the heap and an object will consume memory until the garbage collector removes it from the heap. We'll look at these issues further in Chapter 5 where we discuss object lifecycle in more detail.

To compare and contrast the difference between passing reference types and value types, we also need to reconsider the parameter types discussed in the previous section. We actually have four primary combinations for passing arguments to methods:

  • Passing a value type by value

  • Passing a value type by reference (ref or out)

  • Passing a reference type by value

  • Passing a reference type by reference (ref or out)

The following sections cover each of these combinations in detail. Here's what happens with each of the four possible combinations:

Note

The following sections do not explicitly cover output parameters (out), these are really a variation of a ref parameter.

Passing Value Types by Value

When we think about value types, numbers such as integers tend to be the first kind of value type that springs to mind. Structures are also value types and are ideal candidates for creating complex value type entities such as a type to represent a fraction, coordinates on a graph, or maybe some credit-card details. All of these examples are usually represented as a structured set of numbers. Structures are chosen over classes when dealing with relatively simple real-world objects that cannot be represented using a single primitive type.

When a structure is passed by value, a copy of the structure is made to the method parameter. Both the argument and parameter hold separate copies of the structure. Any changes made by the method to its copy of the structure will not be reflected in the client code.

Passing a value type by value is the deafult, and it is a logical default. You should not consider changing it unless one of the conditions listed in the next section applies.

In the following example, we can demonstrate passing a value type by value. This example is based on a CreditCard entity represented as a structure. We will implement a GetBalance() method that simulates retrieving the credit card balance details from a database. Because of the sensitive nature of credit card details, it is important that bugs cannot creep to the GetBalance() method that might accidentally change the credit card details. The following example is named val_by_val.cs in the source code file.

    using System;    public struct CreditCard    {      double cardNumber;      DateTime expiryDate;      public double CardNumber      {        get        {          return cardNumber;        }        set        {          cardNumber = value;          }        }        public DateTime ExpiryDate        {          get        {          return expiryDate;        }        set        {          expiryDate = value;        }      }    }    public class Authorization    {      public static decimal GetBalance(CreditCard creditCard)      {        creditCard.ExpiryDate = creditCard.ExpiryDate.AddMonths(12);        return (decimal)845.21;      }    }    class Payments    {      static void Main()      {        CreditCard card = new CreditCard();        Decimal balance;        card.CardNumber = 1111222333444;        card.ExpiryDate = Convert.ToDateTime("01/03/2003");        balance = Authorization.GetBalance(card);        Console.WriteLine("Card Number - " + card.CardNumber);        Console.WriteLine("Expiry Date - " +            card.ExpiryDate.ToShortDateString());        Console.WriteLine("Balance     - " + balance);      }    } 

When running this program, you should see the following output in the console window:

    C:\Class Design\Ch 03>val_by_val    Card Number - 1111222333444    Expiry Date - 01/03/2003    Balance     - 845.21 

Note that the deliberate modification of the credit card expiry date in the GetBalance() method has not permanently affected the state of the credit card structure. This is a good example of when to pass a value type by value because we do not want the GetBalance() method to have any side effects.

Passing Value Types by Reference

When we pass a structure using the ref keyword, a reference to the structure is passed to the method, instead of a copy of the structure itself. When this happens both the argument and the parameter are said to have referential equivalence. This means that changes made by the method are visible to the client code.

Passing a value type by reference should be chosen:

  • When the argument can be represented in a simple form such as a number or a simple structure.

  • When the method needs to make changes to the structure that should be reflected in client code.

  • When several structures need to be passed to the method and the method will alter all of them. This resolves the issue of a method only being able to return a single value back to the client code.

To demonstrate when to pass a value type by reference, add the following RenewCard() method to the Authorization class. The new file is called val_by_ref.cs:

    public static void RenewCard(ref CreditCard creditCard)    {      creditCard.ExpiryDate = creditCard.ExpiryDate.AddMonths(12);    } 

Now change the Payments class as follows:

    class Payments    {      static void Main()      {        CreditCard card = new CreditCard();        Decimal balance;        card.CardNumber = 1111222333444;        card.ExpiryDate = Convert.ToDateTime("01/03/2003");        balance = Authorization.GetBalance(card);        Console.WriteLine("Card Number - " + card.CardNumber);        Console.WriteLine("Expiry Date - " +            card.ExpiryDate.ToShortDateString());        Console.WriteLine("Balance     - " + balance);        Authorization.RenewCard(ref card);        Console.WriteLine();        Console.WriteLine("Card Number - " + card.CardNumber);        Console.WriteLine("Expiry Date - " +            card.ExpiryDate.ToShortDateString());        Console.WriteLine("Balance     - " + balance);      }    } 

When running the program, the following output should be displaye in the console window:

    C:\Class Design\Ch 03>val_by_ref    Card Number - 1111222333444    Expiry Date - 01/03/2003    Balance     - 845.21    Card Number - 1111222333444    Expiry Date - 01/03/2004    Balance     - 845.21 

Notice how the credit card expiry date has incremented by 1 year. This is a good example of passing a value type by reference because we want the RenewCard() method to have side effects that are visible in the client code.

Passing Reference Types by Value

When reference types are passed by value, we are asking to pass the value of an object. The value of an object is not the value of one of its properties, or a value returned by one of its methods. The method parameter actually receives the value of the object's location on the heap.

The side effects of passing a class by value are exactly the same as passing a structure by reference. But the route taken by the CLR is a different one. When a structure is passed by reference, the structure is referenced from the called method. The argument and parameter hold a reference to the same structure. When a class instance is passed by value, the reference is copied to the called method. The argument and parameter both hold a reference to the same object on the heap.

The route taken by the CLR might be different but the effects are still the same. If the object is mutable, we can change the state of the object passed by value, and those changes will be visible to the calling code.

Passing a reference type by value is the default; again this is a natural default, and you shouldn't change it under normal circumstances.

When an instance of a reference type is passed in to a method, remember that the reference is still held by the calling code. If you put a copy of the reference into a class variable, say, it refers to the same object that the calling code's reference refers to – and will still refer to that same object after your method has returned. So with reference types, not only can a called method change the state of objects passed in to it by the calling code, the calling code can change objects after passing them in to a method, and have the effects persist. Sometimes this is desirable, sometimes it isn't. If you want to avoid these issues with reference types, consider making your types immutable.

The following example, ref_by_val.cs, is based on an employee entity. The employee will be represented as a class because an employee entity is typically complex. Note that only the Salary property will be used in this example to reduce space. We will then implement a Taxation class and a Calculate() method to calculate the employee's net salary:

    using System;    public class Employee    {      int salary;      public int Salary      {        get        {          return salary;        }        set        {          salary = value;        }      }    }    public class Taxation    {      public static void Calculate(Employee employee)      {        employee.Salary = employee.Salary - (employee.Salary*22/100);      }    }    class Calculate    {      static void Main(string[] args)      {        Employee employee = new Employee();        employee.Salary = 40000;        Console.WriteLine("Gross salary before tax - {0}",            employee.Salary);        Taxation.Calculate(employee);        Console.WriteLine("Net salary after tax - {0}",            employee.Salary);      }    } 

When running the program, the following should be displayed in the console window:

    C:\Class Design\Ch 03>ref_by_val    Gross salary before tax - 40000    Net salary after tax    - 31200 

Notice how the salary has changed even though the Employee was passed by value. This is because Employee is a class and therefore a reference type.

We can avoid this behavior by temporarily reassigning the Employee parameter inside the Calculate() method. The new code file is called ref_by_val2.cs. Change the Calculate() method as follows:

    public class Taxation    {      public static void Calculate(Employee employee)      {        Employee temp_employee = new Employee();        temp_employee.Salary = employee.Salary;        temp_employee.Salary = temp_employee.Salary    ((temp_employee.Salary/100)*22);        employee = temp_employee;      }    } 

This time, when running the program, the following should be displayed in the console window:

    C:\Class Design\Ch 03>ref_by_val2    Gross salary before tax - 40000    Net salary after tax    - 40000 

Notice how the salary has remained unchanged after the method call. By using a temporary instance of an object of the same type as the parameter, then copying each property, we can perform the same calculation on the temporary object. Even after reassigning the parameter to the temporary object at the end of the method, the changes to the Salary property are not visible in the client code. Reassignments of reference types passed by value are not permanent and the client code sees no side effects.

Passing Reference Types by Reference

To pass an object to a method, we pass the variable holding the object reference. If the method parameter is declared as ref or out, the parameter is actually a reference to a reference to the object. We then have a double reference – the method parameter is a reference to the calling argument, which in turn is a reference to our object on the heap.

The difference between passing a reference type by reference and passing a reference type by value is subtle, yet important. Unlike passing reference types by value, passing a reference type by reference allows us to permanently reassign the parameter and the argument to a different object instance. This can improve the safety of passing objects to methods that change the state of an object. A parameterized method can be declared to receive an object as an argument, the method can then create a new second object of the same type and change its state, and then if conditions apply, the parameter object can be reassigned against the second object.

Passing a reference type by reference should be used when:

  • The argument must be represented as a class

  • The method needs to conditionally change the state of the argument

  • The method needs to permanently reassign the parameter to a new instance of the same type

As well as allowing a called method to reassign a reference to a new instance of the same type, it also allows it to unassign it – to set its value to null.

To demonstrate this, change the Calculate() method as follows:

    public class Taxation    {      public static void Calculate(ref Employee employee)      {        Employee temp_employee = new Employee();        temp_employee.Salary = employee.Salary;        temp_employee.Salary = temp_employee.Salary            ((temp_employee.Salary*22/100);        employee = temp_employee;      }    } 

Now change the Calculate class:

    class Calculate    {      static void Main()      {        Employee employee = new Employee();        employee.Salary = 40000;        Console.WriteLine("Gross salary before tax - {0}",            employee.Salary);        Taxation.Calculate(ref employee);        Console.WriteLine("Net salary after tax    - {0}",            employee.Salary);      }    } 

When running the program, the following should be displayed in the console window:

    C:\Class Design\Ch 03>ref_by_ref    Gross salary before tax - 40000    Net salary after tax    - 31200 

Notice how the salary has changed after calling the Calculate() method. We can see in this example that passing a reference type by reference always has side effects in the client code, even when the parameter is assigned to a new instance.

Variable Length Parameter Lists

Sometimes when designing a class, we do not know how many arguments a method should accept. Classes that exhibit this behavior are common in the .NET Framework. The Console.WriteLine() method is a good example. This method accepts a variable number of arguments of type System.Object, which are used as arguments to a string format.

A technique known as a parameter array can be used to code a method that accepts an arbitrary number of arguments. The params keyword can be used in the method declaration to achieve this behavior. The file shown next (var_para.cs) demonstrates this:

Important

The parameter immediately following the params keyword must be declared as a single dimensional array – [ ].

    using System;    class Statistics    {      public static double AverageScores(params int[] scores)      {        int total = 0;        double average = 0;        foreach(int score in scores)        {          total += score;        }        average = total*1.0 / scores.Length;        return average;      }    } 

Create the client code to execute the AverageScores() method:

    public class params_example    {      public static void Main()      {        Console.WriteLine("Average - " +            Statistics.AverageScores(7, 2, 5, 8, 9, 6, 8, 1));        Console.WriteLine("Average - " +            Statistics.AverageScores(3, 4, 2, 5));        Console.ReadLine();      }    } 

which produces the following output:

    C:\Class Design\Ch 03>var_para    Average - 5.75    Average - 3.5 

The way in which the CLR resolves parameter array method calls differs from the normal means of resolution, where the number of arguments matches the parameters list one for one. When a mismatch occurs between the number of arguments and the number of parameters, the compiler checks for the presence of the params keyword in the method declaration. The compiler then creates a temporary internal stack of parameters of the declaring type until there are enough parameters to accept the total number of arguments. In addition, each item in the array is passed by reference. Ensure, therefore, that you know of the side effects if you manipulate the values.

Because of the dynamic processing that occurs to match the arguments against enough parameters, parameter arrays must be the last parameter within the method declaration. For instance, the following code will not compile:

    public static double AverageScores(params int[] Scores, int PassMark)    {      //    } 

Important

The params parameter must be the most rightmost parameter of a method.

Using a parameter array does cause a small additional operation to take place before the method can be called – the parameters need to be packaged up into an array, which must be allocated on the heap – but this is hardly a large performance penalty given the flexibility they provide.

Passing Strings – Immutable Objects

Although string objects are reference types, they are instances of the System.String class. This class overrides some of the typical behavior of a reference type, making strings behave in a similar manner to (but not the same as) a value type.

String objects are immutable – that is, they cannot be changed from their initial value. Simple string operations that seem to change the value of a string actually return a different string object, rather than a modified version of the original. This is an important consideration in methods such as XML and HTML builders that extensively process string parameters, because these activities can drain system resources. As they are reference types, changing a string actually results in two strings on the heap, the original, and the modified version. The string variable will reference the new string object following new assignments. If the only reference to the original string was from the variable which we've changed, then we now have an orphaned string on the heap which the garbage collector will have to find and dispose of. Obviously if we're doing this a lot, we'll have some performance problems.

Important

Extensive string manipulation should be performed using the System.Text.StringBuilder class, which limits the number of objects that have to be created to represent strings.

Note

The Apress publication, C# Text Manipulation Handbook, ISBN 1-86100-823-6, covers the use of the StringBuilder class and other classes to perform efficient text manipulation.

Passing Arrays and Upper Bound Checking

Arrays are reference types in the .NET Framework, and the rules detailed in Passing Reference Types by Value and Passing Reference Types by Reference are observed while dealing with arrays. This even applies to arrays containing value types only.

Changes to the members of an array passed in by value will be visible to client code, because they are accessed through a reference to the same array. However, an array passed by value can't be reassigned as a different array reference, so the size of the array will remain constant. So, if you want to obtain exactly eight bytes of data from a method, you could pass it an eight byte array by value, and have the method fill it; you would be guaranteed that the array, after the method had finished with it, was still an eight byte array, but the contents of each element could have been changed by the called method.

To prevent the client code from gaining access to any manipulation the method performs on the array, create a temporary array within the method. With an array of value types, it is adequate to Clone() the array, because that creates a new array containing a new set of copies of the same values. We can then manipulate these values to our heart's content, without affecting the client code's array. If our array contains reference types, this may not be completely adequate. The Clone() method performs a shallow copy of the array and so the references in the new array point to the same objects on the heap as do the references in the parameter array. If you want the elements in the array to be cloned as well, you'll need to do some more work to ensure the array contents are duplicated as well.

Passing Enumerated Values

Enumerations provide an excellent way to pass arguments to methods where the argument has a finite set of allowable values. Enumerations are used to clarify lists of data internally represented as numerical values such as validation tables and lookup lists. Enumerations can also be considered collections of constants.

Calling a method with an enum parameter makes full use of IntelliSense in the Visual Studio .NET IDE. The developer can select a valid entry from the CloseDownOptions list, or pass a variable declared as the same type as CloseDownOptions. Enums make code clearer and more concise because they eliminate the need for magic numbers (hard-coded values), and multiple constant values, and reduce the risk of an incorrect value being passed to the method.




C# Class Design Handbook(c) Coding Effective Classes
C# Class Design Handbook: Coding Effective Classes
ISBN: 1590592573
EAN: 2147483647
Year: N/A
Pages: 90

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net