Functions

>> Aug 19, 2009

All the code you have seen so far has taken the form of a single block, perhaps with some looping to repeat lines of code, and branching to execute statements conditionally. If you needed to perform an operation on your data, then this has meant placing the code required right where you want it to work.
This kind of code structure is limited. Often, some tasks — such as finding the highest value in an array, for example — may need to be performed at several points in a program. You can place identical (or near identical) sections of code in your application whenever necessary, but this has its own problems. Changing even one minor detail concerning a common task (to correct a code error, for example) may require changes to multiple sections of code, which may be spread throughout the application. Missing one of these could have dramatic consequences and cause the whole application to fail. In addition, the application could get very lengthy.
The solution to this problem is to use functions. Functions in C# are a means of providing blocks of code that can be executed at any point in an application.
Functions of the specific type examined in this chapter are known as methods, but this term has a very specific meaning in .NET programming that will only become clear later in this book. So for now this term is not to be used.
For example, you could have a function that calculates the maximum value in an array. You can use this function from any point in your code, and use the same lines of code in each case. Because you only need to supply this code once, any changes you make to it will affect this calculation wherever it is used. This function can be thought of as containing reusable code.
Functions also have the advantage of making your code more readable, as you can use them to group related code together. This way, your application body itself can be made very short, as the inner workings of the code are separated out. This is similar to the way in which you can collapse regions of code together in the IDE using the outline view, and it gives a more logical structure to your application.
Functions can also be used to create multipurpose code, enabling them to perform the same operations on varying data. You can supply a function with information to work with in the form of parameters, and you can obtain results from functions in the form of return values. In the preceding example, you could supply an array to search as a parameter and obtain the maximum value in the array as a return value. This means that you can use the same function to work with a different array each time. The name and parameters of a function (but not its return type) collectively define the signature of a function.
This chapter covers all of the following topics:
. How to define and use simple functions that don ’ t accept or return any data .
. How to transfer data to and from functions .
. Variable scope, which reflects how data in a C# application is localized to specific regions of code, an issue that becomes especially important when you are separating your code into multiple functions.
. An in - depth look at an important function in C# applications: Main(). You will learn how you can use the built - in behavior of this function to make use of command - line arguments , which enable you to transfer information into applications when you run them.
. Another feature of the struct type shown in the last chapter: the fact that you can supply functions as members of struct types .

The chapter ends with two more advanced topics: function overloading and delegates . Function overloading is a technique that enables you to provide multiple functions with the same name but different parameters. A delegate is a variable type that enables you to use functions indirectly. A delegate can be used to call any function that matches the return type and parameters defined by the delegate, giving you the ability to choose between several functions at runtime.

Defining and Using Functions
This section describes how you can add functions to your applications and then use (call) them from your code. Starting with the basics, you look at simple functions that don ’ t exchange any data with code that calls them, and then look at more advanced function usage. The following Try It Out gets things moving.

Try It Out : Defining and Using a Basic Function
1. Create a new console application called Ch06Ex01 and save it in the directory C:\BegVCSharp\Chapter06.
2. Add the following code to Program.cs:
class Program
{
static void Write()
{
Console.WriteLine(“Text output from function.”);
}
static void Main(string[] args)
{
Write();
Console.ReadKey();
}
}


3. Execute the code. The result is shown in Figure 6 - 1. Figure 6-1

How It Works
The following four lines of your code define a function called Write():
static void Write()
{
Console.WriteLine(“Text output from function.”);
}


The code contained here simply outputs some text to the console window, but this behavior isn ’ t that important at the moment, because the focus here is on the mechanisms behind function definition and use.
The function definition here consists of the following:
. Two keywords: static and void
. A function name followed by parentheses, Write()
. A block of code to execute enclosed in curly braces

Function names are usually written in PascalCasing.

The code that defines the Write()function looks very similar to some of the other code in your application:
static void Main(string[] args)
{
...
}


This is because all the code you have written so far (apart from type definitions) has been part of a function. This function, Main(), is the entry point function for a console application. When a C# application is executed, the entry point function it contains is called; and when this function is completed, the application terminates. All C# executable code must have an entry point.
The only difference between the Main()function and your Write() function (apart from the lines of code they contain) is that there is some code inside the parentheses after the function name Main . This is how you specify parameters, which you see in more detail shortly.
As mentioned earlier, both Main()and Write()are defined using the staticand voidkeywords. The static keyword relates to object - oriented concepts, which you come back to later in the book. For now, you only need to remember that all the functions you use in your applications in this section of the book must use this keyword.
void, in contrast, is much simpler to explain. It ’ s used to indicate that the function does not return a value. Later in this chapter, you see the code that you need to use when a function has a return value.

Moving on, the code that calls your function is as follows:
Write();

You simply type the name of the function followed by empty parentheses. When program execution reaches this point, the code in the Write() function runs.
The parentheses used both in the function definition and where the function is called are mandatory. Try removing them if you like — the code won ’ t compile.

Return Values
The simplest way to exchange data with a function is to use a return value. Functions that have return values evaluate to that value, in exactly the same way that variables evaluate to the values they contain when you use them in expressions. Just like variables, return values have a type.
For example, you might have a function called GetString()whose return value is a string. You could use this in code, such as the following:
string myString;
myString = GetString();


Alternatively, you might have a function called GetVal()that returns a doublevalue, which you could use in a mathematical expression:
double myVal;
double multiplier = 5.3;
myVal = GetVal() * multiplier;


When a function returns a value, you have to modify your function in two ways:
. Specify the type of the return value in the function declaration instead of using the void keyword.
. Use the return keyword to end the function execution and transfer the return value to the calling code.

In code terms, this looks like the following in a console application function of the type you ’ ve been looking at:
static < functionName> ()
{
...
return ;
}


The only limitation here is that < returnValue > must be a value that either is of type < returnType > or can be implicitly converted to that type. However, < returnType > can be any type you want, including the more complicated types you ’ ve seen. This might be as simple as the following:
static double GetVal()
{
return 3.2;
}


However, return values are usually the result of some processing carried out by the function; the preceding could be achieved just as easily using a constvariable.
When the return statement is reached, program execution returns to the calling code immediately. No lines of code after this statement are executed, although this doesn ’ t mean that returnstatements can only be placed on the last line of a function body. You can use return earlier in the code, perhaps after performing some branching logic. Placing returnin a forloop, an if block, or any other structure causes the structure to terminate immediately and the function to terminate, as shown here:
static double GetVal()
{
double checkVal;
// CheckVal assigned a value through some logic (not shown here).
if (checkVal < 5)
return 4.7;
return 3.2;
}


Here, one of two values may be returned, depending on the value of checkVal. The only restriction in this case is that a return statement must be processed before reaching the closing }of the function. The following is illegal:
static double GetVal()
{
double checkVal;
// CheckVal assigned a value through some logic.
if (checkVal < 5)
return 4.7;
}


If checkValis > = 5, then no return statement is met, which isn ’ t allowed. All processing paths must reach a return statement. In most cases, the compiler detects this and gives you the error “ not all code paths return a value. ”
As a final note, return can be used in functions that are declared using the void keyword (that don ’ t have a return value). In that case, the function simply terminates. When you use return in this way, it is an error to provide a return value in between the return keyword and the semicolon that follows.

Parameters
When a function is to accept parameters, you must specify the following:
. A list of the parameters accepted by the function in its definition, along with the types of those parameters.
. A matching list of parameters in each function call .

This involves the following code, where you can have any number of parameters, each with a type and a name:
static < functionName> ( < paramType> , ...)
{
...
return ;
}


The parameters are separated using commas, and each of these parameters is accessible from code within the function as a variable. For example, a simple function might take two doubleparameters and return their product:
static double Product(double param1, double param2)
{
return param1 * param2;
}


The following Try It Out provides a more complex example.
Try It Out : Exchanging Data with a Function (Part 1)
1. Create a new console application called Ch06Ex02 and save it in the directory C:\BegVCSharp\Chapter06.
2. Add the following code to Program.cs:
class Program
{
static int MaxValue(int[] intArray)
{
int maxVal = intArray[0];
for (int i = 1; i < intArray.Length; i++)
{
if (intArray[i] > maxVal)
maxVal = intArray[i];
}
return maxVal;
}
static void Main(string[] args)
{
int[] myArray = {1, 8, 3, 6, 2, 5, 9, 3, 0, 2};
int maxVal = MaxValue(myArray);
Console.WriteLine(“The maximum value in myArray is {0}”, maxVal);
Console.ReadKey();
}
}


3. Execute the code. The result is shown in Figure 6 - 2. Figure 6-2

How It Works
This code contains a function that does what the example function at the beginning of this chapter hoped to do. It accepts an array of integers as a parameter and returns the highest number in the array. The function definition is as follows:
static int MaxValue(int[] intArray)
{
int maxVal = intArray[0];
for (int i = 1; i < intArray.Length; i++)
{
if (intArray[i] > maxVal)
maxVal = intArray[i];
}
return maxVal;
}


The function, MaxValue(), has a single parameter defined, an intarray called intArray. It also has a return type of int. The calculation of the maximum value is simple. A local integer variable called maxVal is initialized to the first value in the array, and then this value is compared with each of the subsequent elements in the array. If an element contains a higher value than maxVal, then this value replaces the current value of maxVal. When the loop finishes, maxValcontains the highest value in the array, and is returned using the returnstatement.
The code in Main()declares and initializes a simple integer array to use with the MaxValue()function:
int[] myArray = {1, 8, 3, 6, 2, 5, 9, 3, 0, 2};

The call to MaxValue()is used to assign a value to the intvariable maxVal:
int maxVal = MaxValue(myArray);

Next, you write this value to the screen using Console.WriteLine():
Console.WriteLine(“The maximum value in myArray is {0}”, maxVal);

Parameter Matching
When you call a function, you must match the parameters as specified in the function definition exactly. This means matching the parameter types, the number of parameters, and the order of the parameters. For example, the function:
static void MyFunction(string myString, double myDouble)
{
...
}


can ’ t be called using the following:
MyFunction(2.6, “Hello”);

Here, you are attempting to pass a doublevalue as the first parameter and a stringvalue as the second parameter, which is not the order in which the parameters are defined in the function definition.
You also can ’ t use:
MyFunction(“Hello”);

Here, you are only passing a single string parameter, where two parameters are required. Attempting to use either of the two preceding function calls will result in a compiler error, because the compiler
forces you to match the signatures of the functions you use.
Recall from the introduction that the signature of a function is defined by the name and parameters of the function.
Going back to the example, MaxValue()can only be used to obtain the maximum intin an array of int values. If you replace the code in Main() with:
static void Main(string[] args)
{
double[] myArray = {1.3, 8.9, 3.3, 6.5, 2.7, 5.3};
double maxVal = MaxValue(myArray);
Console.WriteLine(“The maximum value in myArray is {0}”, maxVal);
Console.ReadKey();
}


then the code won ’ t compile because the parameter type is wrong. In the “ Overloading Functions ” section later in this chapter, you ’ ll learn a useful technique for getting around this problem.

Parameter Arrays
C# enables you to specify one (and only one) special parameter for a function. This parameter, which must be the last parameter in the function definition, is known as a parameter array. Parameter arrays enable you to call functions using a variable amount of parameters and are defined using the params keyword.
Parameter arrays can be a useful way to simplify your code because you don ’ t have to pass arrays from your calling code. Instead, you pass several parameters of the same type, which are placed in an array you can use from within your function.
The following code is required to define a function that uses a parameter array:
static < functionName> ( < p1Type> , ... , params [] )
{
...
return < returnValue > ;
}

You can call this function using code like the following:
( < p1> , ... , , , ...)

Here < val1 > , < val2 > , and so on are values of type < type > , which are used to initialize the < name > array. The number of parameters that you can specify here is almost limitless; the only restriction is that they must all be of type < type > . You can even specify no parameters at all.
This final point makes parameter arrays particularly useful for specifying additional information for functions to use in their processing. For example, suppose you have a function called GetWord()that takes a string value as its first parameter and returns the first word in the string:
string firstWord = GetWord(“This is a sentence.”);

Here, firstWordwill be assigned the string This.
You might add a paramsparameter to GetWord(), enabling you to optionally select an alternative word to return by its index:
string firstWord = GetWord(“This is a sentence.”, 2);

Assuming that you start counting at 1 for the first word, this would result in firstWordbeing assigned the string is.
You might also add the capability to limit the number of characters returned in a third parameter, also accessible through the paramsparameter:
string firstWord = GetWord(“This is a sentence.”, 4, 3);

Here, firstWordwould be assigned the string sen.
Here ’ s a complete example. The following Try It Out defines and uses a function with a paramstype parameter.

Try It Out : Exchanging Data with a Function (Part 2)
1. Create a new console application called Ch06Ex03 and save it in the directory C:\BegVCSharp\Chapter06.
2. Add the following code to Program.cs:
class Program
{
static int SumVals(params int[] vals)
{
int sum = 0;
foreach (int val in vals)
{
sum += val;
}
return sum;
}
static void Main(string[] args)
{
int sum = SumVals(1, 5, 2, 9, 8);
Console.WriteLine(“Summed Values = {0}”, sum);
Console.ReadKey();
}
}


3. Execute the code. The result is shown in Figure 6 - 3. Figure 6-3

How It Works
In this example, the function SumVals()is defined using the params keyword to accept any number of intparameters (and no others):
static int SumVals(params int[] vals)
{
...
}


The code in this function simply iterates through the values in the valsarray and adds the values together, returning the result.
In Main(), you call this function with five integer parameters:
int sum = SumVals(1, 5, 2, 9, 8);

Note that you could just as easily call this function with none, one, two, or a hundred integer parameters — there is no limit to the number you can specify.

Reference and Value Parameters
All the functions defined so far in this chapter have had value parameters. That is, when you have used parameters, you have passed a value into a variable used by the function. Any changes made to this variable in the function have no effect on the parameter specified in the function call. For example, consider a function that doubles and displays the value of a passed parameter:
static void ShowDouble(int val)
{
val *= 2;
Console.WriteLine(“val doubled = {0}”, val);
}


Here, the parameter, val, is doubled in this function. If you call it in the following way:
int myNumber = 5;
Console.WriteLine(“myNumber = {0}”, myNumber);
ShowDouble(myNumber);
Console.WriteLine(“myNumber = {0}”, myNumber);


The text output to the console is as follows:
myNumber = 5
val doubled = 10
myNumber = 5


Calling ShowDouble()with myNumber as a parameter doesn ’ t affect the value of myNumberin Main(), even though the parameter it is assigned to, val, is doubled.
That ’ s all very well, but if you want the value of myNumber to change, you have a problem. You could use a function that returns a new value for myNumber, like this:
static int DoubleNum(int val)
{
val *= 2;
return val;
}


You could call this function using the following:
int myNumber = 5;
Console.WriteLine(“myNumber = {0}”, myNumber);
myNumber = DoubleNum(myNumber);
Console.WriteLine(“myNumber = {0}”, myNumber);


However, this code is hardly intuitive and won ’ t cope with changing the values of multiple variables used as parameters (as functions have only one return value).
Instead, you want to pass the parameter by reference. This means that the function will work with exactly the same variable as the one used in the function call, not just a variable that has the same value. Any changes made to this variable will therefore be reflected in the value of the variable used as a parameter. To do this, you simply use the ref keyword to specify the parameter:
static void ShowDouble(ref int val)
{
val *= 2;
Console.WriteLine(“val doubled = {0}”, val);
}


Then, specify it again in the function call (this is mandatory):
int myNumber = 5;
Console.WriteLine(“myNumber = {0}”, myNumber);
ShowDouble(ref myNumber);
Console.WriteLine(“myNumber = {0}”, myNumber);


The text output to the console is now as follows:
myNumber = 5
val doubled = 10
myNumber = 10


This time myNumberhas been modified by ShowDouble().
Note two limitations on the variable used as a ref parameter. First, the function may result in a change to the value of a reference parameter, so you must use a nonconstant variable in the function call. The following is therefore illegal:
const int myNumber = 5;
Console.WriteLine(“myNumber = {0}”, myNumber);
ShowDouble(ref myNumber);
Console.WriteLine(“myNumber = {0}”, myNumber);


Second, you must use an initialized variable. C# doesn ’ t allow you to assume that a refparameter will be initialized in the function that uses it. The following code is also illegal:
int myNumber;
ShowDouble(ref myNumber);
Console.WriteLine(“myNumber = {0}”, myNumber);


Out Parameters
In addition to passing values by reference, you can also specify that a given parameter is an out parameter by using the out keyword, which is used in the same way as the ref keyword (as a modifier to the parameter in the function definition and in the function call). In effect, this gives you almost exactly the same behavior as a reference parameter in that the value of the parameter at the end of the function execution is returned to the variable used in the function call. However, there are important differences:
. Whereas it is illegal to use an unassigned variable as a ref parameter, you can use an unassigned variable as an out parameter.
. An out parameter must be treated as an unassigned value by the function that uses it.

This means that while it is permissible in calling code to use an assigned variable as an outparameter, the value stored in this variable is lost when the function executes.
As an example, consider an extension to the MaxValue()function shown earlier, which returns the maximum value of an array. Modify the function slightly so that you obtain the index of the element with the maximum value within the array. To keep things simple, obtain just the index of the first occurrence of this value when there are multiple elements with the maximum value. To do this, you add an outparameter by modifying the function as follows:
static int MaxValue(int[] intArray, out int maxIndex)
{
int maxVal = intArray[0];
maxIndex = 0;
for (int i = 1; i < intArray.Length; i++)
{
if (intArray[i] > maxVal)
{
maxVal = intArray[i];
maxIndex = i;
}
}
return maxVal;
}


You might use this function as shown here:
int[] myArray = {1, 8, 3, 6, 2, 5, 9, 3, 0, 2};
int maxIndex;
Console.WriteLine(“The maximum value in myArray is {0}”,
MaxValue(myArray, out maxIndex));
Console.WriteLine(“The first occurrence of this value is at element {0}”,
maxIndex + 1);


This results in the following:
The maximum value in myArray is 9
The first occurrence of this value is at element 7


Note that you must use the out keyword in the function call, just as with the refkeyword.
One has been added to the value of maxIndex returned here when it is displayed onscreen. This is to translate the index to a more readable form, so that the first element in the array is referred to as element 1, rather than element 0.

Variable Scope
Throughout the last section, you may have been wondering why exchanging data with functions is necessary. The reason is that variables in C# are only accessible from localized regions of code. A given variable is said to have a scope from where it is accessible.
Variable scope is an important subject and one best introduced with an example. The following Try It Out illustrates a situation in which a variable is defined in one scope, and an attempt to use it is made in a different scope.

Try It Out : Defining and Using a Basic Function
1. Make the following changes to Ch06Ex01 in Program.cs:
class Program
{
static void Write()
{
Console.WriteLine(“myString = {0}”, myString);
}
static void Main(string[] args)
{
string myString = “String defined in Main()”;
Write();
Console.ReadKey();
}
}


2. Compile the code and note the error and warning that appear in the task list:
The name ‘myString’ does not exist in the current context
The variable ‘myString’ is assigned but its value is never used


How It Works
What went wrong? Well, the variable myStringdefined in the main body of your application (the Main() function) isn ’ t accessible from the Write()function.
The reason for this inaccessibility is that variables have a scope within which they are valid. This scope encompasses the code block that they are defined in and any directly nested code blocks. The blocks of code in functions are separate from the blocks of code from which they are called. Inside Write() , the name myStringis undefined, and the myStringvariable defined in Main()is out of scope — it can only be used from within Main().
In fact, you can have a completely separate variable in Write()called myString. Try modifying the code as follows:
class Program
{
static void Write()
{
string myString = “String defined in Write()”;
Console.WriteLine(“Now in Write()”);
Console.WriteLine(“myString = {0}”, myString);
}
static void Main(string[] args)
{
string myString = “String defined in Main()”;
Write();
Console.WriteLine(“\nNow in Main()”);
Console.WriteLine(“myString = {0}”, myString);
Console.ReadKey();
}
}


This code does compile, resulting in the output shown in Figure 6 - 4. Figure 6-4

The operations performed by this code are as follows:
. Main()defines and initializes a string variable called myString.
. Main()transfers control to Write().
. Write()defines and initializes a string variable called myString, which is a different variable than the myStringdefined in Main().
. Write()outputs a string to the console containing the value of myStringas defined in Write().
. Write()transfers control back to Main().
. Main()outputs a string to the console containing the value of myStringas defined in Main().

Variables whose scopes cover a single function in this way are known as local variables. It is also possible to have global variables, whose scopes cover multiple functions. Modify the code as follows:
class Program
{
static string myString;
static void Write()
{
string myString = “String defined in Write()”;
Console.WriteLine(“Now in Write()”);
Console.WriteLine(“Local myString = {0}”, myString);
Console.WriteLine(“Global myString = {0}”, Program.myString);
}
static void Main(string[] args)
{
string myString = “String defined in Main()”;
Program.myString = “Global string”;
Write();
Console.WriteLine(“\nNow in Main()”);
Console.WriteLine(“Local myString = {0}”, myString);
Console.WriteLine(“Global myString = {0}”, Program.myString);
Console.ReadKey();
}
}


The result is now as shown in Figure 6 - 5. Figure 6-5

Here, you have added another variable called myString, this time further up the hierarchy of names in the code. This variable is defined as follows:
static string myString;

Note that again the static keyword is required here. Without going into too much detail, understand that in this type of console application, you must use either the staticor const keyword for global variables of this form. If you want to modify the value of the global variable, you need to use static because const prohibits the value of the variable from changing.
To differentiate between this variable and the local variables in Main()and Write()with the same names, you have to classify the variable name using a fully qualified name, as described in Chapter 3 .
Here, you refer to the global version as Program.myString. This is only necessary when you have global and local variables with the same name; if there were no local myStringvariable, you could simply use myString to refer to the global variable, rather than Program.myString. When you have a local variable with the same name as a global variable, the global variable is said to be hidden.
The value of the global variable is set in Main()with
Program.myString = “Global string”;

and accessed in Write()with
Console.WriteLine(“Global myString = {0}”, Program.myString);

You might be wondering why you shouldn ’ t just use this technique to exchange data with functions, rather than the parameter passing shown earlier. There are indeed situations where this is the preferable way to exchange data, but there are just as many (if not more) where it isn ’ t. The choice of whether to use global variables depends on the intended use of the function in question. The problem with using global variables is that they are generally unsuitable for “ general - purpose ” functions, which are capable of working with whatever data you supply, not just data in a specific global variable. You look at this in more depth a little later.

Variable Scope in Other Structures
Before moving on, note that one of the points made in the last section has consequences above and beyond variable scope between functions. It was stated that the scopes of variables encompass the code blocks in which they are defined and any directly nested code blocks. This also applies to other code blocks, such as those in branching and looping structures. Consider the following code:
int i;
for (i = 0; i < 10; i++)
{
string text = “Line “ + Convert.ToString(i);
Console.WriteLine(“{0}”, text);
}
Console.WriteLine(“Last text output in loop: {0}”, text);


Here, the string variable textis local to the for loop. This code won ’ t compile because the call to Console.WriteLine()that occurs outside of this loop attempts to use the variable text, which is out of scope outside of the loop. Try modifying the code as follows:
int i;
string text;
for (i = 0; i < 10; i++)
{
text = “Line “ + Convert.ToString(i);
Console.WriteLine(“{0}”, text);
}
Console.WriteLine(“Last text output in loop: {0}”, text);


This code will also fail because variables must be declared and initialized before use, and textis only initialized in the forloop. The value assigned to text is lost when the loop block is exited. However, you can make the following change:
int i;
string text = “”;
for (i = 0; i < 10; i++)
{
text = “Line “ + Convert.ToString(i);
Console.WriteLine(“{0}”, text);
}
Console.WriteLine(“Last text output in loop: {0}”, text);


This time text is initialized outside of the loop, and you have access to its value. The result of this simple code is shown in Figure 6 - 6. Figure 6-6

Here, the last value assigned to text in the loop is accessible from outside the loop. As you can see, this topic requires a bit of effort to come to grips with. It is not immediately obvious why, in light of the earlier example, text doesn ’ t retain the empty string it is assigned before the loop in the code after the loop.
The explanation for this behavior is related to memory allocation for the textvariable, and indeed any variable. Merely declaring a simple variable type doesn ’ t result in very much happening. It is only when values are assigned to the variables that values are allocated a place in memory to be stored. When this allocation takes place inside a loop, the value is essentially defined as a local value and goes out of scope outside of the loop.
Even though the variable itself isn ’ t localized to the loop, the value it contains is. However, assigning a value outside of the loop ensures that the value is local to the main code, and is still in scope inside the loop. This means that the variable doesn ’ t go out of scope before the main code block is exited, so you have access to its value outside of the loop.
Luckily for you, the C# compiler detects variable scope problems, and responding to the error messages it generates certainly helps you to understand the topic of variable scope.
Finally, you should be aware of best practices. In general, it is worth declaring and initializing all variables before any code blocks that use them. An exception to this is when you declare looping variables as part of a loop block:
for (int i = 0; i < 10; i++)
{
...
}


Here, i is localized to the looping code block, but that ’ s fine because you will rarely require access to this counter from external code.

Parameters and Return Values Versus Global Data
This section takes a closer look at exchanging data with functions via global data and via parameters and return values. To recap, consider the following code:
class Program
{
static void ShowDouble(ref int val)
{
val *= 2;
Console.WriteLine(“val doubled = {0}”, val);
}
static void Main(string[] args)
{
int val = 5;
Console.WriteLine(“val = {0}”, val);
ShowDouble(ref val);
Console.WriteLine(“val = {0}”, val);
}
}


This code is slightly different from the code shown earlier in this chapter, when you used the variable name myNumberin Main(). This illustrates the fact that local variables can have identical names and yet not interfere with each other. It also means that the two code samples shown here are more similar, allowing you to focus more on the specific differences without worrying about variable names.
Compare it with this code:
class Program
{
static int val;
static void ShowDouble()
{
val *= 2;
Console.WriteLine(“val doubled = {0}”, val);
}
static void Main(string[] args)
{
val = 5;
Console.WriteLine(“val = {0}”, val);
ShowDouble();
Console.WriteLine(“val = {0}”, val);
}
}


The results of both of these ShowDouble()functions are identical.
There are no hard - and - fast rules for using one technique rather than another, and both techniques are perfectly valid, but you may want to consider the following guidelines.
To start with, as mentioned when this topic was first introduced, the ShowDouble()version that uses the global value only uses the global variable val. To use this version, you must use this global variable. This limits the versatility of the function slightly and means that you must continuously copy the global variable value into other variables if you intend to store the results. In addition, global data might be modified by code elsewhere in your application, which could cause unpredictable results (values might change without you realizing it until it ’ s too late).
However, this loss of versatility can often be a bonus. Sometimes you only want to use a function for one purpose, and using a global data store reduces the possibility that you will make an error in a function call, perhaps passing it the wrong variable.
Of course, it could also be argued that this simplicity actually makes your code more difficult to understand. Explicitly specifying parameters enables you to see at a glance what is changing. If you see a
call that reads myFunction(val1, out val2), you instantly know that val1and val2 are the important variables to consider and that val2will be assigned a new value when the function is completed. Conversely, if this function took no parameters, then you would be unable to make any assumptions about what data it manipulated.
Finally, remember that using global data isn ’ t always possible. Later in this book, you will see code written in different files and/or belonging to different namespaces communicating with each other via functions. In these cases, the code is often separated to such a degree that there is no obvious choice for a global storage location.
To summarize, feel free to use either technique to exchange data. In general, use parameters rather than global data. But there are certainly cases where global data might be more suitable, and it certainly isn ’ t an error to use this technique.

The Main() Function
Now that you ’ ve covered most of the simple techniques used in the creation and use of functions, it ’ s time to take a closer look at the Main()function.
Earlier, you saw that Main()is the entry point for a C# application and that execution of this function encompasses the execution of the application. That is, when execution is initiated, the Main()function executes, and when the Main()function finishes, execution ends.
The Main()function can return either voidor int, and can optionally include a string[] args parameter, so you can use any of the following versions:
static void Main()
static void Main(string[] args)
static int Main()
static int Main(string[] args)


The third and fourth versions return an intvalue, which can be used to signify how the application terminates, and often is used as an indication of an error (although this is by no means mandatory). In general, returning a value of 0 reflects normal termination (that is, the application has completed and can terminate safely).
The optional argsparameter of Main()provides you with a way to obtain information from outside the application, specified at runtime. This information takes the form of command-line parameters.
You may well have come across command - line parameters already. When you execute an application from the command line, you can often specify information directly, such as a file to load on application execution. For example, consider the Notepad application in Windows. You can run Notepad simply by typing Notepad in a command prompt window or in the window that appears when you select the Run option from the Windows Start menu. You can also type something like Notepad “ myfile.txt ” in these locations. The result is that Notepad will either load the file myfile.txt when it runs or offer to create this file if it doesn ’ t already exist. Here, “ myfile.txt ” is a command - line argument. You can write console applications that work similarly by making use of the argsparameter.
When a console application is executed, any specified command - line parameters are placed in this args array. You can then use these parameters in your application. The following Try It Out shows this in action. You can specify any number of command - line arguments, each of which will be output to the console.
Try It Out : Command - Line Arguments
1. Create a new console application called Ch06Ex04 and save it in the directory C:\BegVCSharp\Chapter06.
2. Add the following code to Program.cs:
class Program
{
static void Main(string[] args)
{
Console.WriteLine(“{0} command line arguments were specified:”,
args.Length);
foreach (string arg in args)
Console.WriteLine(arg);
Console.ReadKey();
}
}


3. Open the property pages for the project (right - click on the Ch06Ex04 project name in the Solution Explorer window and select Properties).
4. Select the Debug page and add any command - line arguments you want to the Command line arguments setting. An example is shown in Figure 6 - 7. Figure 6-7

5. Run the application. The output is shown in Figure 6 - 8. Figure 6-8

How It Works
The code used here is very simple:
Console.WriteLine(“{0} command line arguments were specified:”,
args.Length);
foreach (string arg in args)
Console.WriteLine(arg);


You ’ re just using the args parameter as you would any other string array. You ’ re not doing anything fancy with the arguments; you ’ re just writing whatever is specified to the screen. You supplied the arguments via the project properties in the IDE. This is a handy way to use the same command - line arguments whenever you run the application from the IDE, rather than type them at a command - line prompt every time. The same result can be obtained by opening a command prompt window in the same directory as the project output (C:\BegCSharp\Chapter6\Ch06Ex04\Ch06Ex04\bin\Debug) and typing this:
Ch06Ex04 256 myFile.txt “a longer argument”

Each argument is separated from the next by spaces. To supply an argument that includes spaces, you can enclose it in double quotation marks, which prevents it from being interpreted as multiple arguments.

Struct Functions
The last chapter covered struct types for storing multiple data elements in one place. Structs are actually capable of a lot more than this. For example, they can contain functions as well as data. That may seem a little strange at first, but it is in fact very useful. As a simple example, consider the following struct:
struct customerName
{
public string firstName, lastName;
}


If you have variables of type customerName and you want to output a full name to the console, you are forced to build the name from its component parts. You might use the following syntax for a customerNamevariable called myCustomer, for example:
customerName myCustomer;
myCustomer.firstName = “John”;
myCustomer.lastName = “Franklin”;
Console.WriteLine(“{0} {1}”, myCustomer.firstName, myCustomer.lastName);


By adding functions to structs, you can simplify this by centralizing the processing of common tasks. For example, you can add a suitable function to the struct type as follows:
struct customerName
{
public string firstName, lastName;
public string Name ()
{
return firstName + “ “ + lastName;
}
}


This looks much like any other function you ’ ve seen in this chapter, except that you haven ’ t used the static modifier. The reasons for this will become clear later in the book; for now it is enough to know that this keyword isn ’ t required for struct functions. You can use this function as follows:
customerName myCustomer;
myCustomer.firstName = “John”;
myCustomer.lastName = “Franklin”;
Console.WriteLine(myCustomer.Name());


This syntax is much simpler, and much easier to understand, than the previous syntax. Note here that the Name()function has direct access to the firstNameand lastName struct members. Within the customerName struct, they can be thought of as global.

Overloading Functions
Earlier in this chapter, you saw how you must match the signature of a function when you call it. This implies that you need to have separate functions to operate on different types of variables. Function overloading provides you with the capability to create multiple functions with the same name, but each working with different parameter types. For example, earlier you used the following code, which
contained a function called MaxValue():
class Program
{
static int MaxValue(int[] intArray)
{
int maxVal = intArray[0];
for (int i = 1; i < intArray.Length; i++)
{
if (intArray[i] > maxVal)
maxVal = intArray[i];
}
return maxVal;
}
static void Main(string[] args)
{
int[] myArray = {1, 8, 3, 6, 2, 5, 9, 3, 0, 2};
int maxVal = MaxValue(myArray);
Console.WriteLine(“The maximum value in myArray is {0}”, maxVal);
Console.ReadKey();
}
}


This function can only be used with arrays of int values. You could provide different named functions for different parameter types, perhaps renaming the preceding function as IntArrayMaxValue()and adding functions such as DoubleArrayMaxValue()to work with other types. Alternatively, you could just add the following function to your code:
...
static double MaxValue(double[] doubleArray)
{
double maxVal = doubleArray[0];
for (int i = 1; i < doubleArray.Length; i++)
{
if (doubleArray[i] > maxVal)
maxVal = doubleArray[i];
}
return maxVal;
}
...


The difference here is that you are using doublevalues. The function name, MaxValue(), is the same, but (crucially) its signature is different. That ’ s because the signature of a function, as shown earlier, includes both the name of the function and its parameters. It would be an error to define two functions with the same signature, but because these two functions have different signatures, this is fine.
The return type of a function isn ’t part of its signature, so you can ’t define two functions that differ only in return type; they would have identical signatures.
After adding the preceding code, you have two versions of MaxValue(), which accept intand double arrays, returning an intor double maximum, respectively.
The beauty of this type of code is that you don ’ t have to explicitly specify which of these two functions you want to use. You simply provide an array parameter, and the correct function is executed depending on the type of parameter used.
Note another aspect of the IntelliSense feature in VS and VCE: When you have the two functions shown previously in an application and then proceed to type the name of the function, e.g., Main(), the IDE shows you the available overloads for the function. For example, if you type
double result = MaxValue(

the IDE gives you information about both versions of MaxValue(), which you can scroll between using the up and down arrow keys, as shown in Figure 6 - 9. Figure 6-9

All aspects of the function signature are included when overloading functions. You might, for example, have two different functions that take parameters by value and by reference, respectively:
static void ShowDouble(ref int val)
{
...
}
static void ShowDouble(int val)
{
...
}


Deciding which version to use is based purely on whether the function call contains the refkeyword. The following would call the reference version:
ShowDouble(ref val);

This would call the value version:
ShowDouble(val);

Alternatively, you could have functions that differ in the number of parameters they require, and so on.

Delegates
A delegate is a type that enables you to store references to functions. Although this sounds quite involved, the mechanism is surprisingly simple. The most important purpose of delegates will become clear later in the book when you look at events and event handling, but it will be useful to briefly consider them here. Delegates are declared much like functions, but with no function body and using the delegate keyword. The delegate declaration specifies a return type and parameter list. After defining a delegate, you can declare a variable with the type of that delegate. You can then initialize the variable as a reference to any function that has the same return type and parameter list as that delegate. Once you have done this, you can call that function by using the delegate variable as if it were a function.
When you have a variable that refers to a function, you can also perform other operations that would be otherwise impossible. For example, you can pass a delegate variable to a function as a parameter, and then that function can use the delegate to call whatever function it refers to, without knowing what function will be called until runtime. The following Try It Out demonstrates using a delegate to access one of two functions.

Try It Out: Using a Delegate to Call a Function
1. Create a new console application called Ch06Ex05 and save it in the directory C:\BegVCSharp\Chapter06.
2. Add the following code to Program.cs:
class Program
{
delegate double ProcessDelegate(double param1, double param2);
static double Multiply(double param1, double param2)
{
return param1 * param2;
}
static double Divide(double param1, double param2)
{
return param1 / param2;
}
static void Main(string[] args)
{
ProcessDelegate process;
Console.WriteLine(“Enter 2 numbers separated with a comma:”);
string input = Console.ReadLine();
int commaPos = input.IndexOf(‘,’);
double param1 = Convert.ToDouble(input.Substring(0, commaPos));
double param2 = Convert.ToDouble(input.Substring(commaPos + 1,
input.Length - commaPos - 1));
Console.WriteLine(“Enter M to multiply or D to divide:”);
input = Console.ReadLine();
if (input == “M”)
process = new ProcessDelegate(Multiply);
else
process = new ProcessDelegate(Divide);
Console.WriteLine(“Result: {0}”, process(param1, param2));
Console.ReadKey();
}
}


3. Execute the code. The result is shown in Figure 6 - 10. Figure 6-10

How It Works
This code defines a delegate (ProcessDelegate) whose return type and parameters match those of the two functions (Multiply()and Divide()). The delegate definition is as follows:
delegate double ProcessDelegate(double param1, double param2);

The delegate keyword specifies that the definition is for a delegate, rather than a function (the definition appears in the same place that a function definition might). Next, the definition specifies a double return value and two double parameters. The actual names used are arbitrary; you can call the delegate type and parameter names whatever you like. Here, we ’ ve used a delegate name of ProcessDelegateand double parameters called param1and param2.
The code in Main()starts by declaring a variable using the new delegate type:
static void Main(string[] args)
{
ProcessDelegate process;


Next you have some fairly standard C# code that requests two numbers separated by a comma, and then places these numbers in two doublevariables:
Console.WriteLine(“Enter 2 numbers separated with a comma:”);
string input = Console.ReadLine();
int commaPos = input.IndexOf(‘,’);
double param1 = Convert.ToDouble(input.Substring(0, commaPos));
double param2 = Convert.ToDouble(input.Substring(commaPos + 1,
input.Length - commaPos - 1));


For demonstration purposes, no user input validation is included here. If this were “ real ” code, you ’ d spend much more time ensuring that you had valid values in the local param1and param2variables.
Next, you ask the user to multiply or divide these numbers:
Console.WriteLine(“Enter M to multiply or D to divide:”);
input = Console.ReadLine();


Based on the user’ s choice, you initialize the processdelegate variable:
if (input == “M”)
process = new ProcessDelegate(Multiply);
else
process = new ProcessDelegate(Divide);


To assign a function reference to a delegate variable, you use slightly odd - looking syntax. Much like assigning array values, you must use the new keyword to create a new delegate. After this keyword, you specify the delegate type and supply a parameter referring to the function you want to use — namely, the Multiply()or Divide()function. This parameter doesn ’ t match the parameters of the delegate type or the target function; it is a syntax unique to delegate assignment. The parameter is simply the name of the function to use, without any parentheses.
Finally, call the chosen function using the delegate. The same syntax works here, regardless of which function the delegate refers to:
Console.WriteLine(“Result: {0}”, process(param1, param2));
Console.ReadKey();
}


Here, you treat the delegate variable as if it were a function name. Unlike a function, though, you can also perform additional operations on this variable, such as passing it to a function via a parameter, as shown in this simple example:
static void ExecuteFunction(ProcessDelegate process)
{
process(2.2, 3.3);
}


This means that you can control the behavior of functions by passing them function delegates, much like choosing a “ snap - in ” to use. For example, you might have a function that sorts a string array alphabetically. You can use several techniques to sort lists, with varying performance depending on the characteristics of the list being sorted. By using delegates, you can specify the function to use by passing a sorting algorithm function delegate to a sorting function.
There are many such uses for delegates, but, as mentioned earlier, their most prolific use is in event handling, the subject of Chapter 13 .

Summary
This chapter provided a fairly complete overview of the use of functions in C# code. Many of the additional features that functions offer (delegates in particular) are more abstract, and you need to understand them in regard to object - oriented programming, the subject of Chapter 8 .
Following are the highlights of this chapter:
. Defining and using functions in console applications.
. Exchanging data with functions via return values and parameters .
. Passing parameter arrays to functions.
. Passing values by reference or by value .
. Specifying parameters for additional return values .
. The concept of variable scope, whereby variables can be hidden from sections of code where they aren ’ t required .
. Details of the Main()function, including command - line parameter usage .
. Using functions in struct types .
. Function overloading, whereby you can supply different parameters to the same function to get additional functionality.
. Delegates and how to dynamically select functions for runtime execution .

Knowing how to use functions is central to all of the programming you are likely to do. Later chapters, particularly when you get to OOP (from Chapter 8 onward), explain a more formal structure for functions and how they apply to classes. You will likely find that the capability to abstract code into reusable blocks is the most useful aspect of C# programming.

Exercises
1. The following two functions have errors. What are they?
static bool Write()
{
Console.WriteLine(“Text output from function.”);
}
static void myFunction(string label, params int[] args, bool showLabel)
{
if (showLabel)
Console.WriteLine(label);
foreach (int i in args)
Console.WriteLine(“{0}”, i);
}


2. Write an application that uses two command - line arguments to place values into a string and an integer variable, respectively. Then display these values.
3. Create a delegate and use it to impersonate the Console.ReadLine()function when asking for user input.
4. Modify the following struct to include a function that returns the total price of an order:
struct order
{
public string itemName;
public int unitCount;
public double unitCost;
}


5. Add another function to the order struct that returns a formatted string as follows (where italic entries enclosed in angle brackets are replaced by appropriate values):
Order Information: items at $ < unit cost> each, total cost $ < total cost>

Post a Comment

About This Blog

  © Blogger template Simple n' Sweet by Ourblogtemplates.com 2009

Back to TOP