Writing Methods and Applying Scope - Microsoft® Visual C#® 2012 Step by Step (2012)

Microsoft® Visual C#® 2012 Step by Step (2012)

Chapter 3. Writing Methods and Applying Scope

After completing this chapter, you will be able to

§ Declare and call methods.

§ Pass information to a method.

§ Return information from a method.

§ Define local and class scope.

§ Use the integrated debugger to step into and out of methods as they run.

In Chapter 2, you learned how to declare variables, how to create expressions using operators, and how precedence and associativity control the way in which expressions containing multiple operators are evaluated. In this chapter, you’ll learn about methods. You’ll see how to declare and call methods, and you’ll also learn how to use arguments and parameters to pass information to a method as well as return information from a method by using a return statement. You’ll see how to step into and out of methods by using the Microsoft Visual Studio 2012 integrated debugger. This information is useful when you need to trace the execution of your methods if they do not work quite as you expect. Finally, you’ll also learn how to declare methods that take optional parameters and how to invoke methods by using named arguments.

Creating Methods

A method is a named sequence of statements. If you have previously programmed using a language such as C, C++, or Microsoft Visual Basic, you will see that a method is similar to a function or a subroutine. A method has a name and a body. The method name should be a meaningful identifier that indicates the overall purpose of the method (calculateIncomeTax, for example). The method body contains the actual statements to be run when the method is called. Additionally, methods can be given some data for processing and can return information, which is usually the result of the processing. Methods are a fundamental and powerful mechanism.

Declaring a Method

The syntax for declaring a C# method is as follows:

returnType methodName ( parameterList )

{

// method body statements go here

}

§ The returnType is the name of a type and specifies the kind of information the method returns as a result of its processing. This can be any type, such as int or string. If you’re writing a method that does not return a value, you must use the keyword void in place of the return type.

§ The methodName is the name used to call the method. Method names follow the same identifier rules as variable names. For example, addValues is a valid method name, whereas add$Values is not. For now, you should follow the camelCase convention for method names—for example,displayCustomer.

§ The parameterList is optional and describes the types and names of the information that you can pass into the method for it to process. You write the parameters between opening and closing parentheses, ( ), as though you’re declaring variables, with the name of the type followed by the name of the parameter. If the method you’re writing has two or more parameters, you must separate them with commas.

§ The method body statements are the lines of code that are run when the method is called. They are enclosed between opening and closing braces, { }.

IMPORTANT

C, C++, and Microsoft Visual Basic programmers should note that C# does not support global methods. You must write all your methods inside a class; otherwise, your code will not compile.

Here’s the definition of a method called addValues that returns an int result and has two int parameters, leftHandSide and rightHandSide:

int addValues(int leftHandSide, int rightHandSide)

{

// ...

// method body statements go here

// ...

}

NOTE

You must explicitly specify the types of any parameters and the return type of a method. You cannot use the var keyword.

Here’s the definition of a method called showResult that does not return a value and has a single int parameter, called answer:

void showResult(int answer)

{

// ...

}

Notice the use of the keyword void to indicate that the method does not return anything.

IMPORTANT

Visual Basic programmers should notice that C# does not use different keywords to distinguish between a method that returns a value (a function) and a method that does not return a value (a procedure or subroutine). You must always specify either a return type or void.

Returning Data from a Method

If you want a method to return information (that is, its return type is not void), you must include a return statement at the end of the processing in the method body. A return statement consists of the keyword return followed by an expression that specifies the returned value, and a semicolon. The type of the expression must be the same as the type specified by the method declaration. For example, if a method returns an int, the return statement must return an int; otherwise, your program will not compile. Here is an example of a method with a return statement:

int addValues(int leftHandSide, int rightHandSide)

{

// ...

return leftHandSide + rightHandSide;

}

The return statement is usually positioned at the end of the method because it causes the method to finish, and control returns to the statement that called the method, as described later in this chapter. Any statements that occur after the return statement are not executed (although the compiler warns you about this problem if you place statements after the return statement).

If you don’t want your method to return information (that is, its return type is void), you can use a variation of the return statement to cause an immediate exit from the method. You write the keyword return immediately followed by a semicolon. For example:

void showResult(int answer)

{

// display the answer

...

return;

}

If your method does not return anything, you can also omit the return statement because the method finishes automatically when execution arrives at the closing brace at the end of the method. Although this practice is common, it is not always considered good style.

In the following exercise, you will examine another version of the MathsOperators project from Chapter 2. This version has been improved by the careful use of some small methods. Dividing code in this way helps to make it easier to understand and more maintainable.

Examine method definitions

1. Start Visual Studio 2012 if it is not already running.

2. Open the Methods project in the \Microsoft Press\Visual CSharp Step By Step\Chapter 3\Windows X\Methods folder in your Documents folder.

3. On the DEBUG menu, click Start Debugging.

Visual Studio 2012 builds and runs the application. It should look the same as the application from Chapter 2.

4. Refamiliarize yourself with the application and how it works, and then return to Visual Studio. On the DEBUG menu, click Stop Debugging (or click Quit in the Methods window if you are using Windows 7).

5. Display the code for MainWindow.xaml.cs in the Code and Text Editor window (in Solution Explorer, expand the MainWindow.xaml file and then double-click MainWindow.xaml.cs).

6. In the Code and Text Editor window, locate the addValues method.

The method looks like this:

private int addValues(int leftHandSide, int rightHandSide)

{

expression.Text = leftHandSide.ToString() + " + " + rightHandSide.ToString();

return leftHandSide + rightHandSide;

}

NOTE

Don’t worry about the private keyword at the start of the definition of this method for the moment; you will learn what this keyword means in Chapter 7

The addValues method contains two statements. The first statement displays the calculation being performed in the expression text box on the form. The values of the parameters leftHandSide and rightHandSide are converted to strings (using the ToString method you met in Chapter 2) and concatenated together using the string version of the plus operator (+).

The second statement uses the int version of the + operator to add the values of the leftHandSide and rightHandSide int variables together, and then returns the result of this operation. Remember that adding two int values together creates another int value, so the return type of theaddValues method is int.

If you look at the methods subtractValues, multiplyValues, divideValues, and remainderValues, you will see that they follow a similar pattern.

7. In the Code and Text Editor window, locate the showResult method.

The showResult method looks like this:

private void showResult(int answer)

{

result.Text = answer.ToString();

}

This method contains one statement that displays a string representation of the answer parameter in the result text box. It does not return a value, so the type of this method is void.

TIP

There is no minimum length for a method. If a method helps to avoid repetition and makes your program easier to understand, the method is useful regardless of how small it is.

There is also no maximum length for a method, but usually you want to keep your method code small enough to get the job done. If your method is more than one screen in length, consider breaking it into smaller methods for readability.

Calling Methods

Methods exist to be called! You call a method by name to ask it to perform its task. If the method requires information (as specified by its parameters), you must supply the information requested. If the method returns information (as specified by its return type), you should arrange to capture this information somehow.

Specifying the Method Call Syntax

The syntax of a C# method call is as follows:

result = methodName ( argumentList )

§ The methodName must exactly match the name of the method you’re calling. Remember, C# is a case-sensitive language.

§ The result = clause is optional. If specified, the variable identified by result contains the value returned by the method. If the method is void (that is, it does not return a value), you must omit the result = clause of the statement. If you don’t specify the result = clause and the method does return a value, the method runs but the return value is discarded.

§ The argumentList supplies the information that the method accepts. You must supply an argument for each parameter, and the value of each argument must be compatible with the type of its corresponding parameter. If the method you’re calling has two or more parameters, you must separate the arguments with commas.

IMPORTANT

You must include the parentheses in every method call, even when calling a method that has no arguments.

To clarify these points, take a look at the addValues method again:

int addValues(int leftHandSide, int rightHandSide)

{

// ...

}

The addValues method has two int parameters, so you must call it with two comma-separated int arguments, such as this:

addValues(39, 3); // okay

You can also replace the literal values 39 and 3 with the names of int variables. The values in those variables are then passed to the method as its arguments, like this:

int arg1 = 99;

int arg2 = 1;

addValues(arg1, arg2);

If you try to call addValues in some other way, you will probably not succeed for the reasons described in the following examples:

addValues; // compile-time error, no parentheses

addValues(); // compile-time error, not enough arguments

addValues(39); // compile-time error, not enough arguments

addValues("39", "3"); // compile-time error, wrong types for arguments

The addValues method returns an int value. This int value can be used wherever an int value can be used. Consider these examples:

int result = addValues(39, 3); // on right-hand side of an assignment

showResult(addValues(39, 3)); // as argument to another method call

The following exercise continues with the Methods application. This time, you will examine some method calls.

Examine method calls

1. Return to the Methods project. (This project is already open in Visual Studio 2012 if you’re continuing from the previous exercise. If you are not, open it from the \Microsoft Press\Visual CSharp Step By Step\Chapter 3\Windows X\Methods folder in your Documents folder.)

2. Display the code for MainWindow.xaml.cs in the Code and Text Editor window.

3. Locate the calculateClick method, and look at the first two statements of this method after the try statement and opening brace. (You will learn about try statements in Chapter 6)

These statements look like this:

int leftHandSide = System.Int32.Parse(lhsOperand.Text);

int rightHandSide = System.Int32.Parse(rhsOperand.Text);

These two statements declare two int variables, called leftHandSide and rightHandSide. Notice the way in which the variables are initialized. In both cases, the Parse method of the System.Int32 class is called. (System is a namespace, and Int32 is the name of the class in this namespace.) You have seen this method before—it takes a single string parameter and converts it to an int value. These two lines of code take whatever the user has typed into the lhsOperand and rhsOperand text box controls on the form and converts them to int values.

4. Look at the fourth statement in the calculateClick method (after the if statement and another opening brace):

calculatedValue = addValues(leftHandSide, rightHandSide);

This statement calls the addValues method, passing the values of the leftHandSide and rightHandSide variables as its arguments. The value returned by the addValues method is stored in the calculatedValue variable.

5. Look at the next statement:

showResult(calculatedValue);

This statement calls the showResult method, passing the value in the calculatedValue variable as its argument. The showResult method does not return a value.

6. In the Code and Text Editor window, find the showResult method you looked at earlier.

The only statement of this method is this:

result.Text = answer.ToString();

Notice that the ToString method call uses parentheses even though there are no arguments.

TIP

You can call methods belonging to other objects by prefixing the method with the name of the object. In the preceding example, the expression answer.ToString() calls the method named ToString belonging to the object called answer.

Applying Scope

You create variables to hold values. You can create variables at various points in your applications. For example, the calculateClick method in the Methods project creates an int variable called calculatedValue and assigns it an initial value of zero, like this:

private void calculateClick(object sender, RoutedEventArgs e)

{

int calculatedValue = 0;

...

}

This variable comes into existence at the point where it is defined, and subsequent statements in the calculateClick method can then use this variable. This is an important point: a variable can be used only after it has been created. When the method has finished, this variable disappears and cannot be used elsewhere.

When a variable can be accessed at a particular location in a program, the variable is said to be in scope at that location. The calculatedValue variable has method scope; it can be accessed throughout the calculateClick method but not outside of that method. You can also define variables with different scope; for example, you can define a variable outside of a method but within a class, and this variable can be accessed by any method within that class. Such a variable is said to have class scope.

To put it another way, the scope of a variable is simply the region of the program in which that variable is usable. Scope applies to methods as well as variables. The scope of an identifier (of a variable or method) is linked to the location of the declaration that introduces the identifier in the program, as you learn next.

Defining Local Scope

The opening and closing braces that form the body of a method define the scope of the method. Any variables you declare inside the body of a method are scoped to that method; they disappear when the method ends and can be accessed only by code running in that method. These variables are called local variables because they are local to the method in which they are declared; they are not in scope in any other method.

The scope of local variables means that you cannot use them to share information between methods. Consider this example:

class Example

{

void firstMethod()

{

int myVar;

...

}

void anotherMethod()

{

myVar = 42; // error - variable not in scope

...

}

}

This code fails to compile because anotherMethod is trying to use the variable myVar, which is not in scope. The variable myVar is available only to statements in firstMethod that occur after the line of code that declares myVar.

Defining Class Scope

The opening and closing braces that form the body of a class define the scope of that class. Any variables you declare inside the body of a class (but not inside a method) are scoped to that class. The proper C# term for a variable defined by a class is field. As mentioned earlier, in contrast with local variables, you can use fields to share information between methods. Here is an example:

class Example

{

void firstMethod()

{

myField = 42; // ok

...

}

void anotherMethod()

{

myField++; // ok

...

}

int myField = 0;

}

The variable myField is defined in the class but outside the methods firstMethod and another Method. Therefore, myField has class scope and is available for use by all methods in the class.

There is one other point to notice about this example. In a method, you must declare a variable before you can use it. Fields are a little different. A method can use a field before the statement that defines the field—the compiler sorts out the details for you.

Overloading Methods

If two identifiers have the same name and are declared in the same scope, they are said to be overloaded. Often an overloaded identifier is a bug that gets trapped as a compile-time error. For example, if you declare two local variables with the same name in the same method, the compiler reports an error. Similarly, if you declare two fields with the same name in the same class, or two identical methods in the same class, you also get a compile-time error. This fact might seem hardly worth mentioning, given that everything so far has turned out to be a compile-time error. However, there is a way that you can overload an identifier for a method, and that way is both useful and important.

Consider the WriteLine method of the Console class. You have already used this method for writing a string to the screen. However, when you type WriteLine in the Code and Text Editor window when writing C# code, notice that Microsoft IntelliSense gives you 19 different options! Each version of the WriteLine method takes a different set of parameters; one version takes no parameters and simply outputs a blank line, another version takes a bool parameter and outputs a string representation of its value (True or False), yet another implementation takes a decimal parameter and outputs it as a string, and so on. At compile time, the compiler looks at the types of the arguments you are passing in and then arranges for your application to call the version of the method that has a matching set of parameters. Here is an example:

static void Main()

{

Console.WriteLine("The answer is ");

Console.WriteLine(42);

}

Overloading is primarily useful when you need to perform the same operation on different data types or varying groups of information. You can overload a method when the different implementations have different sets of parameters—that is, when they have the same name but a different number of parameters, or when the types of the parameters differ. When you call a method, you supply a comma-separated list of arguments, and the number and type of the arguments are used by the compiler to select one of the overloaded methods. However, note that although you can overload the parameters of a method, you can’t overload the return type of a method. In other words, you can’t declare two methods with the same name that differ only in their return type. (The compiler is clever, but not that clever.)

Writing Methods

In the following exercises, you’ll create a method that calculates how much a consultant would charge for a given number of consultancy days at a fixed daily rate. You will start by developing the logic for the application and then use the Generate Method Stub Wizard to help you write the methods that are used by this logic. Next, you’ll run these methods in a console application to get a feel for the program. Finally, you’ll use the Visual Studio 2012 debugger to step into and out of the method calls as they run.

Develop the logic for the application

1. Using Visual Studio 2012, open the DailyRate project in the \Microsoft Press\Visual CSharp Step By Step\Chapter 3\Windows X\DailyRate folder in your Documents folder.

2. In Solution Explorer, double-click the file Program.cs in the DailyRate project to display the code for the program in the Code and Text Editor window.

This program is simply a test harness for you to try out your code. When the application starts running, it calls the run method. You add the code that you want to try to the run method. (The way in which the method is called requires an understanding of classes, which you look at inChapter 7.)

3. Add the following statements shown in bold to the body of the run method, between the opening and closing braces:

4. void run()

5. {

6. double dailyRate = readDouble("Enter your daily rate: ");

7. int noOfDays = readInt("Enter the number of days: ");

8. writeFee(calculateFee(dailyRate, noOfDays));

}

The block of code you have just added to the run method calls the readDouble method (which you will write shortly) to ask the user for the daily rate for the consultant. The next statement calls the readInt method (which you will also write) to obtain the number of days. Finally, thewriteFee method (to be written) is called to display the results on the screen. Notice that the value passed to writeFee is the value returned by the calculateFee method (the last one you will need to write), which takes the daily rate and the number of days and calculates the total fee payable.

NOTE

You have not yet written the readDouble, readInt, writeFee, and calculateFee methods, so IntelliSense does not display these methods when you type this code. Do not try to build the application yet—it will fail.

Write the methods using the Generate Method Stub Wizard

1. In the Code and Text Editor window, right-click the readDouble method call in the run method.

A shortcut menu appears that contains useful commands for generating and editing code, as shown here:

image with no caption

2. On the shortcut menu, point to Generate, and then click Method Stub.

Visual Studio examines the call to the readDouble method, ascertains the type of its parameters and return value, and generates a method with a default implementation, like this:

private double readDouble(string p)

{

throw new NotImplementedException();

}

The new method is created with the private qualifier, which is described in Chapter 7. The body of the method currently just throws a NotImplementedException exception. (Exceptions are described in Chapter 6.) You replace the body with your own code in the next step.

3. Delete the throw new NotImplementedException(); statement from the readDouble method, and replace it with the following lines of code:

4. Console.Write(p);

5. string line = Console.ReadLine();

return double.Parse(line);

This block of code displays the string in variable p to the screen. This variable is the string parameter passed in when the method is called, and it contains the message prompting the user to type in the daily rate.

NOTE

The Console.Write method is similar to the Console.WriteLine statement that you have used in earlier exercises, except that it does not output a newline character after the message.

The user types a value, which is read into a string using the ReadLine method and converted to a double using the double.Parse method. The result is passed back as the return value of the method call.

NOTE

The ReadLine method is the companion method to WriteLine; it reads user input from the keyboard, finishing when the user presses the Enter key. The text typed by the user is passed back as the return value. The text is returned as a string value.

6. In the run method, right-click the call to the readInt method in the run method, point to Generate, and then click Method Stub to generate the readInt method.

The readInt method is generated, like this:

private int readInt(string p)

{

throw new NotImplementedException();

}

7. Replace the throw new NotImplementedException(); statement in the body of the readInt method with the following code:

8. Console.Write(p);

9. string line = Console.ReadLine();

return int.Parse(line);

This block of code is similar to the code for the readDouble method. The only difference is that the method returns an int value, so the string typed by the user is converted to a number using the int.Parse method.

10.Right-click the call to the calculateFee method in the run method, point to Generate, and then click Method Stub.

The calculateFee method is generated, like this:

private object calculateFee(double dailyRate, int noOfDays)

{

throw new NotImplementedException();

}

Notice in this case that Visual Studio uses the names of the arguments passed in to generate names for the parameters. (You can, of course, change the parameter names if they are not suitable.) What is more intriguing is the type returned by the method, which is object. Visual Studio is unable to determine exactly which type of value should be returned by the method from the context in which it is called. The object type just means a “thing,” and you should change it to the type you require when you add the code to the method. You will learn more about the objecttype in Chapter 7.

11.Change the definition of the calculateFee method so that it returns a double, as shown in bold type here:

12.private double calculateFee(double dailyRate, int noOfDays)

13.{

14. throw new NotImplementedException();

}

15.Replace the body of the calculateFee method with the following statement, which calculates the fee payable by multiplying the two parameters together and then returns it:

return dailyRate * noOfDays;

16.Right-click the call to the writeFee method in the run method, and then click Generate Method Stub.

Note that Visual Studio uses the definition of the calculateFee method to work out that its parameter should be a double. Also, the method call does not use a return value, so the type of the method is void:

private void writeFee(double p)

{

...

}

TIP

If you feel sufficiently comfortable with the syntax, you can also write methods by typing them directly into the Code and Text Editor window. You do not always have to use the Generate menu option.

17.Replace the code in the body of the writeFee method with the following statement, which calculates the fee and adds a 10% commission:

Console.WriteLine("The consultant's fee is: {0}", p * 1.1);

NOTE

This version of the WriteLine method demonstrates the use of a format string. The text {0} in the string used as the first argument to the WriteLine method is a placeholder that is replaced with the value of the expression following the string (p * 1.1) when it is evaluated at run time. Using this technique is preferable to alternatives, such as converting the value of the expression p * 1.1to a string and using the + operator to concatenate it to the message.

18.On the BUILD menu, click Build Solution.

REFACTORING CODE

A very useful feature of Visual Studio 2012 is the ability to refactor code.

Occasionally, you will find yourself writing the same (or similar) code in more than one place in an application. When this occurs, highlight and right-click the block of code you have just typed, and on the Refactor menu, click Extract Method. The Extract Method dialog box appears, prompting you for the name of a new method to create containing this code. Type a name and click OK. The new method is created containing your code, and the code you typed is replaced with a call to this method. Extract Method is also able to determine whether the method should take any parameters and return a value.

Test the program

1. On the DEBUG menu, click Start Without Debugging.

Visual Studio 2012 builds the program and then runs it. A console window appears.

2. At the Enter Your Daily Rate prompt, type 525 and then press Enter.

3. At the Enter the Number of Days prompt, type 17 and then press Enter.

The program writes the following message to the console window:

The consultant's fee is: 9817.5

4. Press the Enter key to close the application and return to Visual Studio 2012.

In the next exercise, you’ll use the Visual Studio 2012 debugger to run your program in slow motion. You’ll see when each method is called (which is referred to as stepping into the method) and then see how each return statement transfers control back to the caller (also known as stepping out of the method). While you are stepping into and out of methods, you can use the tools on the Debug toolbar. However, the same commands are also available on the DEBUG menu when an application is running in debug mode.

Step through the methods by using the Visual Studio 2012 debugger

1. In the Code and Text Editor window, find the run method.

2. Move the mouse to the first statement in the run method:

double dailyRate = readDouble("Enter your daily rate: ");

3. Right-click anywhere on this line, and click Run to Cursor on the shortcut menu.

The program starts and runs until it reaches the first statement in the run method, and then it pauses. A yellow arrow in the left margin of the Code and Text Editor window indicates the current statement, which is also highlighted with a yellow background.

image with no caption

4. On the VIEW menu, point to Toolbars, and then make sure that the Debug toolbar is selected.

If it was not already visible, the Debug toolbar opens. It might appear docked with the other toolbars. If you cannot see the toolbar, try using the Toolbars command on the VIEW menu to hide it, and notice which buttons disappear. Then display the toolbar again. The Debug toolbar looks like this:

image with no caption

5. On the Debug toolbar, click the Step Into button. (This is the sixth button from the left in the Debug toolbar.)

This action causes the debugger to step into the method being called. The yellow cursor jumps to the opening brace at the start of the readDouble method.

6. Click Step Into again. The cursor advances to the first statement:

Console.Write(p);

TIP

You can also press F11 rather than repeatedly clicking Step Into on the Debug toolbar.

7. On the Debug toolbar, click Step Over. (This is the seventh button from the left.)

This action causes the method to execute the next statement without debugging it (stepping into it). This action is primarily useful if the statement calls a method but you don’t want to step through every statement in that method. The yellow cursor moves to the second statement of the method, and the program displays the Enter Your Daily Rate prompt in a console window before returning to Visual Studio 2012. (The console window might be hidden behind Visual Studio.)

TIP

You can also press F10 rather than clicking Step Over on the Debug toolbar.

8. On the Debug toolbar, click Step Over again.

This time, the yellow cursor disappears and the console window gets the focus because the program is executing the Console.ReadLine method and is waiting for you to type something.

9. Type 525 in the console window, and then press Enter.

Control returns to Visual Studio 2012. The yellow cursor appears on the third line of the method.

10.Hover the mouse over the reference to the line variable on either the second or third line of the method. (It doesn’t matter which.)

A ScreenTip appears, displaying the current value of the line variable (“525”). You can use this feature to make sure that a variable has been set to an expected value while stepping through methods.

image with no caption

11.On the Debug toolbar, click Step Out.

This action causes the current method to continue running uninterrupted to its end. The readDouble method finishes, and the yellow cursor is placed back at the first statement of the run method. This statement as now finished running.

TIP

You can also press Shift+F11 rather than clicking Step Out on the Debug toolbar.

12.On the Debug toolbar, click Step Into.

The yellow cursor moves to the second statement in the run method:

int noOfDays = readInt("Enter the number of days: ");

13.On the Debug toolbar, click Step Over.

This time, you have chosen to run the method without stepping through it. The console window appears again, prompting you for the number of days.

14.In the console window, type 17 and then press Enter.

Control returns to Visual Studio 2012 (you may need to bring Visual Studio to the foreground). The yellow cursor moves to the third statement of the run method:

writeFee(calculateFee(dailyRate, noOfDays));

15.On the Debug toolbar, click Step Into.

The yellow cursor jumps to the opening brace at the start of the calculateFee method. This method is called first, before writeFee, because the value returned by this method is used as the parameter to writeFee.

16.On the Debug toolbar, click Step Out.

The calculateFee method call completes, and the yellow cursor jumps back to the third statement of the run method.

17.On the Debug toolbar, click Step Into.

This time, the yellow cursor jumps to the opening brace at the start of the writeFee method.

18.Place the mouse over the p parameter in the method definition.

The value of p, 8925.0, is displayed in a ScreenTip.

19.On the Debug toolbar, click Step Out.

The message “The consultant’s fee is: 9817.5” is displayed in the console window. (You might need to bring the console window to the foreground to display it if it is hidden behind Visual Studio 2012.) The yellow cursor returns to the third statement in the run method.

20.On the DEBUG menu, click Continue to cause the program to continue running without stopping at each statement.

The application completes and finishes running. Notice that the Debug toolbar disappears when the application finishes—by default, it displays only when you are running an application in debug mode.

Using Optional Parameters and Named Arguments

You have seen that by defining overloaded methods, you can implement different versions of a method that take different parameters. When you build an application that uses overloaded methods, the compiler determines which specific instances of each method it should use to satisfy each method call. This is a common feature of many object-oriented languages, not just C#.

However, developers can use other languages and technologies for building Windows applications and components that do not follow these rules. A key feature of C# and other languages designed for the .NET Framework is the ability to interoperate with applications and components written by using other technologies. One of the principal technologies that underpins many Microsoft Windows applications and services running outside of the .NET Framework is the Component Object Model, or COM (actually, the common language runtime used by the .NET Framework is also heavily dependent on COM, as is the Windows Runtime of Windows 8). COM does not support overloaded methods, but instead it uses methods that can take optional parameters. To make it easier to incorporate COM libraries and components into a C# solution, C# also supports optional parameters.

Optional parameters are also useful in other situations. They provide a compact and simple solution when it is not possible to use overloading because the types of the parameters do not vary sufficiently to enable the compiler to distinguish between implementations. For example, consider the following method:

public void DoWorkWithData(int intData, float floatData, int moreIntData)

{

...

}

The DoWorkWithData method takes three parameters: two ints and a float. Now suppose you want to provide an implementation of DoWorkWithData that took only two parameters: intData and floatData. You can overload the method like this:

public void DoWorkWithData(int intData, float floatData)

{

...

}

If you write a statement that calls the DoWorkWithData method, you can provide either two or three parameters of the appropriate types, and the compiler uses the type information to determine which overload to call:

int arg1 = 99;

float arg2 = 100.0F;

int arg3 = 101;

DoWorkWithData(arg1, arg2, arg3); // Call overload with three parameters

DoWorkWithData(arg1, arg2); // Call overload with two parameters

However, suppose you want to implement two further versions of DoWorkWithData that take only the first parameter and the third parameter. You might be tempted to try this:

public void DoWorkWithData(int intData)

{

...

}

public void DoWorkWithData(int moreIntData)

{

...

}

The issue is that to the compiler, these two overloads appear identical. Your code will fail to compile and will instead generate the error “Type ‘typename’ already defines a member called ‘DoWorkWithData’ with the same parameter types.” To understand why this is so, if this code was legal, consider the following statements:

int arg1 = 99;

int arg3 = 101;

DoWorkWithData(arg1);

DoWorkWithData(arg3);

Which overload or overloads would the calls to DoWorkWithData invoke? Using optional parameters and named arguments can help to solve this problem.

Defining Optional Parameters

You specify that a parameter is optional when you define a method by providing a default value for the parameter. You indicate a default value by using the assignment operator. In the optMethod method shown next, the first parameter is mandatory because it does not specify a default value, but the second and third parameters are optional:

void optMethod(int first, double second = 0.0, string third = "Hello")

{

...

}

You must specify all mandatory parameters before any optional parameters.

You can call a method that takes optional parameters in the same way that you call any other method: you specify the method name and provide any necessary arguments. The difference with methods that take optional parameters is that you can omit the corresponding arguments, and themethod will use the default value when the method runs. In the following example code, the first call to the optMethod method provides values for all three parameters. The second call specifies only two arguments, and these values are applied to the first and second parameters. The thirdparameter receives the default value of “Hello” when the method runs.

optMethod(99, 123.45, "World"); // Arguments provided for all three parameters

optMethod(100, 54.321); // Arguments provided for first two parameters only

Passing Named Arguments

By default, C# uses the position of each argument in a method call to determine which parameters they apply to. Hence, the second example method shown in the previous section passes the two arguments to the first and second parameters in the optMethod method, because this is the order in which they occur in the method declaration. C# also enables you to specify parameters by name, and this feature lets you pass the arguments in a different sequence. To pass an argument as a named parameter, you specify the name of the parameter, a colon, and the value to use. The following examples perform the same function as those shown in the previous section, except that the parameters are specified by name:

optMethod(first : 99, second : 123.45, third : "World");

optMethod(first : 100, second : 54.321);

Named arguments give you the ability to pass arguments in any order. You can rewrite the code that calls the optMethod method like this:

optMethod(third : "World", second : 123.45, first : 99);

optMethod(second : 54.321, first : 100);

This feature also enables you to omit arguments. For example, you can call the optMethod method and specify values for the first and third parameters only and use the default value for the second parameter like this:

optMethod(first : 99, third : "World");

Additionally, you can mix positional and named arguments. However, if you use this technique, you must specify all the positional arguments before the first named argument:

optMethod(99, third : "World"); // First argument is positional

Resolving Ambiguities with Optional Parameters and Named Arguments

Using optional parameters and named arguments can result in some possible ambiguities in your code. You need to understand how the compiler resolves these ambiguities; otherwise, you might find your applications behaving in unexpected ways. Suppose that you define the optMethodmethod as an overloaded method, as shown in the following example:

void optMethod(int first, double second = 0.0, string third = "Hello")

{

...

}

void optMethod(int first, double second = 1.0, string third = "Goodbye", int fourth = 100 )

{

...

}

This is perfectly legal C# code that follows the rules for overloaded methods. The compiler can distinguish between the methods because they have different parameter lists. However, a problem can arise if you attempt to call the optMethod method and omit some of the arguments corresponding to one or more of the optional parameters:

optMethod(1, 2.5, "World");

Again, this is perfectly legal code, but which version of the optMethod method does it run? The answer is that it runs the version that most closely matches the method call, so it invokes the method that takes three parameters and not the version that takes four. That makes good sense, so consider this one:

optMethod(1, fourth : 101);

In this code, the call to optMethod omits arguments for the second and third parameters, but it specifies the fourth parameter by name. Only one version of optMethod matches this call, so this is not a problem. The next one will get you thinking, though!

optMethod(1, 2.5);

This time, neither version of the optMethod method exactly matches the list of arguments provided. Both versions of the optMethod method have optional parameters for the second, third, and fourth arguments. So does this statement call the version of optMethod that takes three parameters and use the default value for the third parameter, or does it call the version of optMethod that takes four parameters and use the default value for the third and fourth parameters? The answer is that it does neither. This is an unresolvable ambiguity, and the compiler does not let you compile the application. The same situation arises with the same result if you try and call the optMethod method as shown in any of the following statements:

optMethod(1, third : "World");

optMethod(1);

optMethod(second : 2.5, first : 1);

In the final exercise in this chapter, you will practice implementing methods that take optional parameters and calling them using named arguments. You will also test common examples of how the C# compiler resolves method calls that involve optional parameters and named arguments.

Define and call a method that takes optional parameters

1. Using Visual Studio 2012, open the DailyRate project in the \Microsoft Press\Visual CSharp Step By Step\Chapter 3\Windows X\DailyRate Using Optional Parameters folder in your Documents folder.

2. In Solution Explorer, double-click the file Program.cs in the DailyRate project to display the code for the program in the Code and Text Editor window.

This version of the application is empty apart from the Main method and the skeleton version of the run method.

3. In the Program class, add the calculateFee method below the run method. This is the same version of the method that you implemented in the previous set of exercises, except that it takes two optional parameters with default values. The method also prints a message indicating the version of the calculateFee method that was called. (You add overloaded implementations of this method in the following steps.)

4. private double calculateFee(double dailyRate = 500.0, int noOfDays = 1)

5. {

6. Console.WriteLine("calculateFee using two optional parameters");

7. return dailyRate * noOfDays;

}

8. Add another implementation of the calculateFee method to the Program class as shown next. This version takes one optional parameter, called dailyRate, of type double. The body of the method calculates and returns the fee for a single day only.

9. private double calculateFee(double dailyRate = 500.0)

10.{

11. Console.WriteLine("calculateFee using one optional parameter");

12. int defaultNoOfDays = 1;

13. return dailyRate * defaultNoOfDays;

}

14.Add a third implementation of the calculateFee method to the Program class. This version takes no parameters and uses hardcoded values for the daily rate and number of days.

15.private double calculateFee()

16.{

17. Console.WriteLine("calculateFee using hardcoded values");

18. double defaultDailyRate = 400.0;

19. int defaultNoOfDays = 1;

20. return defaultDailyRate * defaultNoOfDays;

}

21.In the run method, add the following statements that call calculateFee and display the results, as shown below in bold:

22.public void run()

23.{

24. double fee = calculateFee();

25. Console.WriteLine("Fee is {0}", fee);

}

26.On the DEBUG menu, click Start Without Debugging to build and run the program. The program runs in a console window and displays the following messages:

27.calculateFee using hardcoded values

Fee is 400

The run method called the version of calculateFee that takes no parameters rather than either of the implementations that take optional parameters because it is the version that most closely matches the method call.

Press any key to close the console window and return to Visual Studio.

28.In the run method, modify the statement that calls calculateFee as shown in bold type in this code sample:

29.public void run()

30.{

31. double fee = calculateFee(650.0);

32. Console.WriteLine("Fee is {0}", fee);

}

33.On the DEBUG menu, click Start Without Debugging to build and run the program. The program displays the following messages:

34.calculateFee using one optional parameter

Fee is 650

This time, the run method called the version of calculateFee that takes one optional parameter. As before, this is because this is the version that most closely matches the method call.

Press any key to close the console window and return to Visual Studio.

35.In the run method, modify the statement that calls calculateFee again:

36.public void run()

37.{

38. double fee = calculateFee(500.0, 3);

39. Console.WriteLine("Fee is {0}", fee);

}

40.On the DEBUG menu, click Start Without Debugging to build and run the program. The program displays the following messages:

41.calculateFee using two optional parameters

Fee is 1500

As you might expect from the previous two cases, the run method called the version of calculateFee that takes two optional parameters.

Press any key to close the console window and return to Visual Studio.

42.In the run method, modify the statement that calls calculateFee and specify the dailyRate parameter by name:

43.public void run()

44.{

45. double fee = calculateFee(dailyRate : 375.0);

46. Console.WriteLine("Fee is {0}", fee);

}

47.On the DEBUG menu, click Start Without Debugging to build and run the program. The program displays the following messages:

48.calculateFee using one optional parameter

Fee is 375

As earlier, the run method called the version of calculateFee that takes one optional parameter. Changing the code to use a named argument does not change the way in which the compiler resolves the method call in this example.

Press any key to close the console window and return to Visual Studio.

49.In the run method, modify the statement that calls calculateFee and specify the noOfDays parameter by name:

50.public void run()

51.{

52. double fee = calculateFee(noOfDays : 4);

53. Console.WriteLine("Fee is {0}", fee);

}

54.On the DEBUG menu, click Start Without Debugging to build and run the program. The program displays the following messages:

55.calculateFee using two optional parameters

Fee is 2000

This time, the run method called the version of calculateFee that takes two optional parameters. The method call has omitted the first parameter (dailyRate) and specified the second parameter by name. This is the only version of the calculateFee method that matches the call.

Press any key to close the console window and return to Visual Studio.

56.Modify the implementation of the calculateFee method that takes two optional parameters. Change the name of the first parameter to theDailyRate and update the return statement, as shown in bold type in the following code:

57.private double calculateFee(double theDailyRate = 500.0, int noOfDays = 1)

58.{

59. Console.WriteLine("calculateFee using two optional parameters");

60. return theDailyRate * noOfDays;

}

61.In the run method, modify the statement that calls calculateFee and specify the theDailyRate parameter by name:

62.public void run()

63.{

64. double fee = calculateFee(theDailyRate : 375.0);

65. Console.WriteLine("Fee is {0}", fee);

}

66.On the DEBUG menu, click Start Without Debugging to build and run the program. The program displays the following messages:

67.calculateFee using two optional parameters

Fee is 375

The previous time that you specified the fee but not the daily rate (step 13), the run method called the version of calculateFee that takes one optional parameter. This time, the run method called the version of calculateFee that takes two optional parameters. In this case, using a named argument has changed the way in which the compiler resolves the method call. If you specify a named argument, the compiler compares the argument name to the names of the parameters specified in the method declarations and selects the method that has a parameter with a matching name.

Press any key to close the console window and return to Visual Studio.

Summary

In this chapter, you learned how to define methods to implement a named block of code. You saw how to pass parameters into methods and how to return data from methods. You also saw how to call a method, pass arguments, and obtain a return value. You learned how to define overloaded methods with different parameter lists, and you saw how the scope of a variable determines where it can be accessed. Then you used the Visual Studio 2012 debugger to step through code as it runs. Finally, you learned how to write methods that take optional parameters and how to call methods by using named parameters.

§ If you want to continue to the next chapter

Keep Visual Studio 2012 running, and turn to Chapter 4.

§ If you want to exit Visual Studio 2012 now

On the FILE menu, click Exit. If you see a Save dialog box, click Yes and save the project.

Chapter 3 Quick Reference

To

Do this

Declare a method

Write the method inside a class. Specify the method name, parameter list, and return type, followed by the body of the method between braces. For example:

int addValues(int leftHandSide, int rightHandSide)

{

...

}

Return a value from inside a method

Write a return statement inside the method. For example:

return leftHandSide + rightHandSide;

Return from a method before the end of the method

Write a return statement inside the method. For example:

return;

Call a method

Write the name of the method together with any arguments between parentheses. For example:

addValues(39, 3);

Use the Generate Method Stub Wizard

Right-click a call to the method, and then click Generate Method Stub on the shortcut menu.

Display the Debug toolbar

On the VIEW menu, point to Toolbars, and then click Debug.

Step into a method

On the Debug toolbar, click Step Into.

or

On the DEBUG menu, click Step Into.

Step out of a method

On the Debug toolbar, click Step Out.

or

On the DEBUG menu, click Step Out.

Specify an optional parameter to a method

Provide a default value for the parameter in the method declaration. For example:

void optMethod(int first, double second = 0.0,

string third = "Hello")

{

...

}

Pass a method argument as a named parameter

Specify the name of the parameter in the method call. For example:

optMethod(first : 100, third : "World");