Chapter 3. We Have Our Methods

In This Chapter

  • Defining a method

  • Passing arguments to a method

  • Getting results back

  • Reviewing the WriteLine() method

Programmers need to be able to break large programs into smaller chunks that are easy to handle. For example, the programs contained in previous chapters of this minibook reach the limit of the amount of programming information a person can digest at one time.

C# lets you divide your class code into chunks known as methods. Properly designed and implemented methods can greatly simplify the job of writing complex programs.

Note

A method is equivalent to a function, procedure, or subroutine in other languages. The difference is that a method is always part of a class.

Defining and Using a Method

Consider the following example:

class Example
{
  public int anInt;                  // Nonstatic
  public static int staticInt        // Static
  public void InstanceMethod()       // Nonstatic
  {
    Console.WriteLine("this is an instance method");
  }
  public static void ClassMethod()   // Static
  {
    Console.WriteLine("this is a class method");
  }
}

The element anInt is a data member, just like those shown in Book I. However, the element InstanceMethod() is new. InstanceMethod() is known as an instance method (duh!), which is a set of C# statements that you can execute by referencing the method's name. This concept is best explained by example — even I'm confused right now. (Main() and WriteLine() are used in nearly every example in this book, and they're methods.)

Note: The distinction between static and nonstatic members is important. I cover part of that story in this chapter and continue in more detail in Chapter 4 of this minibook with a focus on nonstatic, or instance, methods.

Note

To invoke a nonstatic — instance — method, you need an instance of the class. To invoke a static — class — method, you call via the class name, not an instance. The following code snippet assigns a value to the object data member anInt and the class, or static, member staticInt:

Example example = new Example(); // Create an instance of class Example.
example.anInt = 1;               // Initialize instance member through instance.
Example.staticInt = 2;           // Initialize class member through class.

The following snippet defines and accesses InstanceMethod() and ClassMethod() in almost the same way:

Example example = new Example(); // Create an instance.
example.InstanceMethod();        // Invoke the instance method
                                 // with that instance.
Example.ClassMethod();           // Invoke the class method with the class.
// The following lines won't compile.
example.ClassMethod();           // Can't access class methods via an instance.
Example.InstanceMethod();        // Can't access instance methods via a class.

Note

Every instance of a class has its own, private copy of any instance members. But all instances of the same class share the same class members — both data members and methods — and their values.

The expression example.InstanceMethod() passes control to the code contained within the method. C# follows an almost identical process for Example.ClassMethod(). Executing the lines just shown (after commenting out the last two lines, which don't compile) generates this output:

this is an instance method
this is a class method

Note

After a method completes execution, it returns control to the point where it was called. That is, control moves to the next statement after the call.

The bit of C# code given in the two sample methods does nothing more than write a silly string to the console, but methods generally perform useful (and sometimes complex) operations such as calculate sines, concatenate two strings, sort an array of students, or surreptitiously e-mail your URL to Microsoft (not really). A method can be as large and complex as you want, but try to strive for shorter methods, using the approach described next.

Tip

I include the parentheses when describing methods in text — as in InstanceMethod() — to make them a little easier to recognize. Otherwise, you might become confused trying to understand what I'm saying.

A Method Example for Your Files

In this section, I divide the monolithic CalculateInterestTable programs from Book I, Chapter 5 into several reasonable methods; the demonstration shows how the proper definition of methods can help make a program easier to write and understand. The process of dividing working code this way is known as refactoring, and Visual Studio 2010 provides a handy Refactor menu that automates the most common refactorings.

Note

I explain the exact details of the method definitions and method calls in later sections of this chapter. This example simply gives an overview.

Tip

By reading the comments with the C# code removed, you should be able to get a good idea of a program's intention. If you cannot, you aren't commenting properly. Conversely, if you can't strip out most comments and still understand the intention from the method and variable names, you aren't naming your methods clearly enough or aren't making them small enough (or both). Smaller methods are preferable, and using good method names beat using comments. (That's why real-world code has far fewer comments than the code examples in this book. I comment more heavily here to explain more.)

In outline form, the CalculateInterestTable program appears this way:

public static void Main(string[] args)
{
  // Prompt user to enter source principal.
  // If the principal is negative, generate an error message.
  // Prompt user to enter the interest rate.
  // If the interest is negative, generate an error message.
  // Finally, prompt user to input the number of years.
  // Display the input back to the user.
  // Now loop through the specified number of years.
  while(year <= duration)
  {
    // Calculate the value of the principal plus interest.
    // Output the result.
  }
}

This bit of code illustrates a good technique for planning a method. If you stand back and study the program from a distance, you can see that it's divided into these three sections:

  • An initial input section in which the user inputs the principal, interest, and duration information

  • A section mirroring the input data so that the user can verify the entry of the correct data

  • A section that creates and outputs the table

Use this list to start looking for ways to refactor the program. In fact, if you further examine the input section of that program, you can see that the same basic code is used to input these amounts:

  • Principal

  • Interest

  • Duration

Your observation gives you another good place to look. Alternatively, you can write empty methods for some of those comments and then fill them in one by one. That's programming by intention.

I used the techniques for planning a method to create the following version of the CalculateInterestTableWithMethods program:

Note

// CalculateInterestTableWithMethods -- Generate an interest table
//    much like the other interest table programs, but this time using a
//    reasonable division of labor among several methods.
using System;
namespace CalculateInterestTableWithMethods
{
  public class Program
  {
    public static void Main(string[] args)
    { // Section 1 -- Input the data you need to create the table.
      decimal principal = 0M;
      decimal interest = 0M;
      decimal duration = 0M;
      InputInterestData(ref principal, ref interest, ref duration);
      // Section 2 -- Verify the data by mirroring it back to the user.
      Console.WriteLine();  // Skip a line.
      Console.WriteLine("Principal     = " + principal);
      Console.WriteLine("Interest      = " + interest + "%");
      Console.WriteLine("Duration      = " + duration + " years");
      Console.WriteLine();
      // Section 3 -- Finally, output the interest table.
      OutputInterestTable(principal, interest, duration);
      // Wait for user to acknowledge the results.
      Console.WriteLine("Press Enter to terminate...");
      Console.Read();
    }
    // InputInterestData -- Retrieve from the keyboard the
    //    principal, interest, and duration information needed
    //    to create the future value table. (Implements Section 1.)
    public static void InputInterestData(ref decimal principal,
                                         ref decimal interest,
                                         ref decimal duration)
    {
// 1a -- Retrieve the principal.
      principal = InputPositiveDecimal("principal");
      // 1b -- Now enter the interest rate.
      interest = InputPositiveDecimal("interest");
      // 1c -- Finally, the duration
      duration = InputPositiveDecimal("duration");
    }
    // InputPositiveDecimal -- Return a positive decimal number
    //    from the keyboard.
    public static decimal InputPositiveDecimal(string prompt)
    {
      // Keep trying until the user gets it right.
      while(true)
      {
        // Prompt the user for input.
        Console.Write("Enter " + prompt + ":");
        // Retrieve a decimal value from the keyboard.
        string input = Console.ReadLine();
        decimal value = Convert.ToDecimal(input);
        // Exit the loop if the value that's entered is correct.
        if (value >= 0)
        {
          // Return the valid decimal value entered by the user.
          return value;
        }
        // Otherwise, generate an error on incorrect input.
        Console.WriteLine(prompt + " cannot be negative");
        Console.WriteLine("Try again");
        Console.WriteLine();
      }
    }
    // OutputInterestTable -- Given the principal and interest,
    //    generate a future value table for the number of periods
    //    indicated in duration. (Implements Section 3.)
    public static void OutputInterestTable(decimal principal,
                                           decimal interest,
                                           decimal duration)
    {
      for (int year = 1; year <= duration; year++)
      {
        // Calculate the value of the principal plus interest.
        decimal interestPaid;
        interestPaid = principal * (interest / 100);
        // Now calculate the new principal by adding
        // the interest to the previous principal.
        principal = principal + interestPaid;
        // Round off the principal to the nearest cent.
        principal = decimal.Round(principal, 2);
        // Output the result.
        Console.WriteLine(year + "-" + principal);
      }
    }
  }
}

I divided the Main() method into three clearly distinguishable parts, each marked with boldfaced comments. I further divided the first section into subsections labeled 1a, 1b, and 1c.

Note

Normally, you don't include the boldfaced comments. If you did, the listings would grow rather complicated because of all the numbers and letters. In practice, those types of comments aren't necessary if the methods are well thought out and their names clearly express the intent of each one.

Part 1 calls the method InputInterestData() to input the three variables the program needs in order to create the table: principal, interest, and duration. Part 2 displays these three values for verification just as earlier versions of the program do. Part 3 outputs the table via the method OutputInterestTable().

From the bottom and working upward, the OutputInterestTable() method contains an output loop with the interest rate calculations. This loop is the same one used in the inline, nonmethod CalculateInterestTable program. The advantage of this version, however, is that when writing this section of code, you don't need to concern yourself with any details of inputting or verifying data. When writing this method, think of it this way: "Given the three numbers — principal, interest, and duration — output an interest table," and that's it. After you're done, you can return to the line that called the OutputInterestTable() method and continue from there.

Note

OutputInterestTable() offers a target for trying the Visual Studio 2010 Refactor menu. Take these steps to give it a whirl:

  1. Using the CalculateInterestTableMoreForgiving example from Book I, Chapter 5 as a starting point, select the code from the declaration of the year variable through the end of the while loop:

    int year = 0;              // You grab the loop variable
    while(year <= duration)    // and the entire while loop.
    {
      //...
    }
  2. Choose Refactor

    A Method Example for Your Files
  3. In the Extract Method dialog box, type OutputInterestTable. Examine the Preview Method Signature box.

    Notice that the proposed signature for the new method begins with the private static keywords and includes principal, interest, and duration in parentheses. (I introduce private, an alternative to public, in Book I. For now, you can make the method public after the refactoring, if you like.)

    private static decimal OutputInterestTable(decimal principal,
      decimal interest, int duration)
  4. Click OK and then Apply to complete the Extract Method refactoring.

    The code you selected in Step 1 is moved to a new method, located below Main() and named OutputInterestTable(). In the spot that it formerly occupied, you see this method call:

    principal = OuputInterestTable(principal, interest, duration);

    The Preview Changes window shows two panes so that you can preview exactly which information will change. The top pane shows the code you're fixing as it looks now. The lower pane shows the code as it will look when it changes. For more sweeping refactorings, each pane may have numerous lines. You can select or deselect them individually to determine which specific elements to refactor.

Tip

If, after refactoring, you suffer "buyer's remorse," click Undo or press Ctrl+Z.

Suppose that the previous refactoring did something you don't like, such as fail to include principal as a parameter. (Because this situation is possible, you must always check a refactoring to be certain that it's what you want.)

This situation happened the first time this section was written, in fact. (I'm writing this chapter based on beta software.) Rather than make principal a parameter, the Extract Method refactoring made it a local variable. To move principal into the parameter list, you can use the Promote Local Variable to Parameter refactoring. But before you do that, you need to initialize the local variable principal (the promotion refactoring doesn't work if the variable is uninitialized):

decimal principal = 0M;  // M means decimal.

To carry out the Promote Local Variable to Parameter refactoring, you right-click the local principal variable's name and click Promote Local Variable to Parameter to move the local variable definition to the parameter list:

private static decimal OutputInterestTable(decimal interest,
                                             int duration,
                                           decimal principal) ...

However, principal is at the end of the list and you want it at the beginning. You can use the Reorder Parameters refactoring to fix this problem.

To carry out Reorder Parameters, right-click the new principal parameter and click the Reorder Parameters link. In the dialog box, click the principal line to select it. Then click the ↑ button twice to move principal to the top of the list. Click OK.

The result of all this refactoring consists of these two pieces:

  • A new private static method below Main(), named Output InterestTable()

  • The following line of code within Main() where the extracted code was:

    principal = OutputInterestTable(principal, interest, duration);

Cool! The same divide-and-conquer logic holds for InputInterestData(). However, that type of refactoring is more complex, so I do it by hand and don't show the steps. A description of the full art of refactoring is beyond the scope of this book, though you can find an introduction at csharp102.info. Experiment with the Refactor menu and use it often for building better-factored code. But always check the results.

Tip

Because C# 4.0 now supports named parameters, you can call the parameters in whichever order you want, by specifying the name of the expected parameter before the value when calling a method. I cover more of this topic later in Book 8, but in C# 4.0, reordering parameters is less of an issue.

For InputInterestData(), you can focus solely on inputting the three decimal values. However, in this case, you realize that inputting each one involves identical operations on three different input variables. The InputPositiveDecimal() method bundles these operations into a set of general code that you can apply to principal, interest, and duration alike. Notice that the three while loops that take input in the original program are collapsed into one while loop inside InputPositiveDecimal(). This process reduces code duplication.

Tip

Avoid duplicating code. Refactoring folks call it the worst "code smell."

This InputPositiveDecimal() method displays the prompt it was given and awaits input from the user. The method returns the value to the caller if it isn't negative. If the value is negative, the method outputs an error message and loops back to try again.

From the user's standpoint, the modified program acts exactly the same as the inline version, which is just the point:

Enter principal:100
Enter interest:−10
interest cannot be negative
Try again

Enter interest:10
Enter duration:10

Principal     = 100
Interest      = 10%
Duration      = 10 years

1-110.0
2-121.00
3-133.10
4-146.41
5-161.05
6-177.16
7-194.88
8-214.37
9-235.81
10-259.39
Press Enter to terminate...

I've refactored a lengthy, somewhat difficult program into smaller, more understandable pieces — while reducing some duplication. As they say in my neck of the woods, "You can't beat that with a stick."

Having Arguments with Methods

A method such as the following example is about as useful as Bill Sempf's hairbrush because no data passes into or out of the method:

public static void Output()
{
  Console.WriteLine("this is a method");
}

Compare this example to real-world methods that do something. For example, the mathematical sine operation requires some type of input — after all, you have to calculate the sine of something. Similarly, to concatenate two strings, you need two strings. So the Concatenate() method requires at least two strings as input. (As The Beav might say, "Gee, Wally, that sounds logical.") You need to find a way to move data into and out of a method.

Passing an argument to a method

The values you input to a method are method arguments, or parameters. Most methods require some type of arguments if they're going to do something. (In this way, methods remind me of my son: We need to have an argument before he'll do anything.) You pass arguments to a method by listing them in the parentheses that follow the method name. Consider this small addition to the earlier Example class:

public class Example
{
  public static void Output(string someString)
  {
    Console.WriteLine("Output() was passed the argument: " + someString);
  }
}

I could invoke this method from within the same class, like this:

Output("Hello");

I would then see this not-too-exciting output:

Output() was passed the argument: Hello

The program passes to the method Output() a reference to the string "Hello". The method receives the reference and assigns it the name someString. The Output() method can use someString within the method just as it would use any other string variable.

I can change the example in one minor way:

string myString = "Hello";
Output(myString);

This code snippet assigns the variable myString to reference the string "Hello". The call Output(myString) passes the object referenced by myString, which is your old friend "Hello". Figure 3-1 depicts this process. From there, the effect is the same as before.

Copying the value of myString to someString.

Figure 3-1. Copying the value of myString to someString.

Note

The placeholders you specify for arguments when you write a method — for example, someString in Output() — are parameters. The values you pass to a method via a parameter are arguments. I use the terms more or less interchangeably in this book.

A similar idea is passing arguments to a program. For example, you may have noticed that Main() usually takes an array argument.

Passing multiple arguments to methods

When I ask my daughter to wash the car, she usually gives me more than just a single argument. Because she has lots of time on the couch to think about it, she can keep several at the ready.

You can define a method with multiple arguments of varying types. Consider the following sample method AverageAndDisplay():

Note

// AverageAndDisplay -- Demonstrate argument passing.
using System;
namespace Example
{
  public class Program
  {
    public static void Main(string[] args)
    {
      // Access the member method.
      AverageAndDisplay("grade 1", 3.5, "grade 2", 4.0);
      // Wait for user to acknowledge.
      Console.WriteLine("Press Enter to terminate...");
      Console.Read();
    }
// AverageAndDisplay -- Average two numbers with their
    //    labels and display the results.
    public static void AverageAndDisplay(string s1, double d1,                                                                string s2, double d2)
    {
      double average = (d1 + d2) / 2;
      Console.WriteLine("The average of "  + s1
                      + " whose value is " + d1
                      + " and "            + s2
                      + " whose value is " + d2
                      + " is " + average);
    }
  }
}

Executing this simple program generates this output:

The average of grade 1 whose value is 3.5 and grade 2 whose value is 4 is 3.75
Press Enter to terminate...

The method AverageAndDisplay() is declared with several parameters in the order in which arguments are to be passed to them.

As usual, execution of the sample program begins with the first statement after Main(). The first noncomment line in Main() invokes the method AverageAndDisplay(), passing the two strings "grade 1" and "grade 2" and the two double values 3.5 and 4.0.

The method AverageAndDisplay() calculates the average of the two double values, d1 and d2, passed to it along with their names contained in s1 and s2, and the calculated average is stored in average.

Tip

Changing the value of an argument inside the method can lead to confusion and errors, so be wise and assign the value to a temporary variable and modify it instead.

Matching argument definitions with usage

Each argument in a method call must match the method definition in both type and order if you call them without naming them. The following (illegal) example generates a build-time error:

Note

// AverageWithCompilerError -- This version does not compile!
using System;
namespace Example
{
  public class Program
  {
    public static void Main(string[] args)
    {
      // Access the member method.
      AverageAndDisplay("grade 1", "grade 2", 3.5, 4.0);
// Wait for user to acknowledge.
      Console.WriteLine("Press Enter to terminate...");
      Console.Read();
    }
    // AverageAndDisplay -- Average two numbers with their
    //    labels and display the results.
    public static void AverageAndDisplay(string s1, double d1,
                                         string s2, double d2)
    {
      // var okay here, but it's really double.
      var average = (d1 + d2) / 2;
      Console.WriteLine("The average of "  + s1
                      + " whose value is " + d1
                      + " and "            + s2
                      + " whose value is " + d2
                      + " is " + average);
    }
  }
}

C# can't match the type of each argument in the call to AverageAnd Display() with the corresponding argument in the method definition. The string "grade 1" matches the first string in the method definition; however, the method definition calls for a double as its second argument rather than the string that's passed.

You can easily see that I simply transposed the second and third arguments. (That's what I hate about computers — they take me too literally. I know what I said, but it's obvious what I meant!)

To fix the problem, swap the second and third arguments.

Overloading a method doesn't mean giving it too much to do

Tip

You can give two methods within a given class the same name — known as overloading the method name — as long as their arguments differ.

This example demonstrates overloading:

Note

// AverageAndDisplayOverloaded -- This version demonstrates that
//    the AverageAndDisplay method can be overloaded.
using System;
namespace AverageAndDisplayOverloaded
{
  public class Program
  {
    public static void Main(string[] args)
    {
      // Access the first version of the method.
      AverageAndDisplay("my GPA", 3.5, "your GPA", 4.0);
      Console.WriteLine();
      // Access the second version of the method.
AverageAndDisplay(3.5, 4.0);
      // Wait for user to acknowledge.
      Console.WriteLine("Press Enter to terminate...");
      Console.Read();
    }
    // AverageAndDisplay -- Average two numbers with their
    //    labels and display the results.
    public static void AverageAndDisplay(string s1, double d1,
                                         string s2, double d2)
    {
      double average = (d1 + d2) / 2;
      Console.WriteLine("The average of "  + s1
                      + " whose value is " + d1);
      Console.WriteLine("and "            + s2
                      + " whose value is " + d2
                      + " is " + average);
    }
    public static void AverageAndDisplay(double d1, double d2)
    {
      double average = (d1 + d2) / 2;
      Console.WriteLine("The average of " + d1
                      + " and "           + d2
                      + " is " + average);
    }
  }
}

This program defines two versions of AverageAndDisplay(). The program invokes one and then the other by passing the proper arguments. C# can tell which method the program wants by comparing the call with the definition. The program compiles properly and generates this output when executed:

The average of my GPA whose value is 3.5
and your GPA whose value is 4 is 3.75

The average of 3.5 and 4 is 3.75
Press Enter to terminate...

C# generally doesn't allow two methods in the same class to have the same name unless the number or type of the methods' arguments differs (or if both differ). Thus C# differentiates between these two methods:

  • AverageAndDisplay(string, double, string, double)

  • AverageAndDisplay(double, double)

When you say it that way, it's clear that the two methods are different.

Implementing default arguments

Often, you want to supply two (or more) versions of a method:

  • Complicated: Provides complete flexibility but requires numerous arguments from the calling routine, several of which the user may not even understand.

Note

The word user doesn't always refer to the user of a program. References to the user of a method often mean, in practice, the programmer who is making use of the method. Another term is client (which is often you).

  • Acceptable (if somewhat bland): Assumes default values for certain arguments.

You can easily implement default arguments using method overloading. Consider this pair of DisplayRoundedDecimal() methods:

Note

// MethodsWithDefaultArguments -- Provide variations of the same methods,
//    some with default arguments, by overloading the method name.
using System;
namespace MethodsWithDefaultArguments
{
  public class Program
  {
    public static void Main(string[] args)
    {
      // Access the member method.
      Console.WriteLine(DisplayRoundedDecimal(12.345678M, 3));
      // Wait for user to acknowledge.
      Console.WriteLine("Press Enter to terminate...");
      Console.Read();
    }
    // DisplayRoundedDecimal -- Convert a decimal value into a string
    //    with the specified number of signficant digits.
    public static string DisplayRoundedDecimal(decimal value,
                                                   int numberOfSignificantDigits)
    {
      // First round off the number to the specified number
      // of significant digits.
      decimal roundedValue =
                     decimal.Round(value,
                                   numberOfSignificantDigits);
      // Convert that to a string.
      string s = Convert.ToString(roundedValue);
      return s;
    }
    public static string DisplayRoundedDecimal(decimal value)
    {
      // Invoke DisplayRoundedDecimal(decimal, int) specifying
      // the default number of digits.
      string s = DisplayRoundedDecimal(value, 2);
      return s;
    }
  }
}

The DisplayRoundedDecimal(decimal, int) method converts the decimal value that's provided into a string with the specified number of digits after the decimal point. Because decimals are often used to display monetary values, the most common choice is to place two digits after the decimal point. Therefore, the DisplayRoundedDecimal(decimal) method provides the same conversion service — but defaults the number of significant digits to two, thereby removing any worry about the meaning of the second argument.

Note

The generic (decimal) version of the method calls the more specific (decimal, int) version to perform its magic. This stacked method calling is more common than not because it reduces duplication. The generic methods simply provide arguments that the programmer doesn't have the inclination to find in the documentation, and shouldn't have to unless she needs them.

Warning

Having to unnecessarily consult the reference documentation for the meanings of normally defaulted arguments distracts the programmer from the main job at hand — thereby making it more difficult, wasting time, and increasing the likelihood of mistakes.

Providing default arguments does more than just save lazy programmers from exerting a tiny bit of effort (programming requires lots of concentration). The author of the method understands the relationship between the arguments — and therefore bears the onus of providing friendlier, overloaded versions of methods.

Visual Basic, C, and C++ programmers should be accustomed to supplying a default value for a parameter directly in the method signature. Until C# 4.0 was released, you couldn't do that in C#. Now you can.

For instance, although overloading the method in the preceding example is a perfectly acceptable way to implement a default parameter, you can also give default parameters by using the equal sign (=), just as in Visual Basic:

// MethodsWithDefaultArguments2-– Provide optional parameters to a method
// to avoid overloading. It's another way to do the same thing.
using System;
namespace MethodsWithDefaultArguments2
{
  public class Program
  {
    public static void Main(string[] args)
    {
      // Access the member method.
      Console.WriteLine(DisplayRoundedDecimal(12.345678M, 3));
      // Wait for user to acknowledge.
      Console.WriteLine("Press Enter to terminate...");
      Console.Read();
    }
    // DisplayRoundedDecimal -- Convert a decimal value into a string
    //    that has the specified number of signficant digits. That argument
    //    is optional and has a default value. If you call the method
    //    without the second argument, it uses the default value.
    public static string DisplayRoundedDecimal(decimal value,
                                      int numberOfSignificantDigits = 2)
    {
      // First round off the number to the specified number
      // of significant digits.
decimal roundedValue =
                     decimal.Round(value,
                                   numberOfSignificantDigits);
      // Convert that to a string.
      string s = Convert.ToString(roundedValue);
      return s;
    }
  }
}

Note

Why would Microsoft make these changes? The answer is COM. The Component Object Model (COM) was the architectural paradigm of choice for Microsoft products before .NET was released, and it's still quite prevalent. (Notice that I didn't say popular.) Office, for one, is entirely developed using COM. COM applications are developed in C++ or Visual Basic 6 and earlier, and methods from those classes allow for optional parameters. Thus, communicating with COM without using optional parameters can become difficult. To address this imbalance, optional parameters (along with a number of other features) were added to C# 4.0.

I cover named and optional parameters ad nauseam in Book III.

Returning Values after Christmas

Many real-world operations create values to return to the caller. For example, Sin() accepts an argument and returns the trigonometric sine. A method can return a value to the caller in two ways — most commonly via the return statement; however, a second method uses the call-by-reference feature.

Returning a value via return postage

The following code snippet demonstrates a small method that returns the average of its input arguments:

public class Example
{
  public static double Average(double d1, double d2)
  {
    double average = (d1 + d2) / 2;
    return average;
  }
  public static void Main(string[] args)
  {
    double v1 = 1.0;
    double v2 = 3.0;
    double averageValue = Average(v1, v2);
    Console.WriteLine("The average of " + v1
                    + " and " + v2 + " is "
                    + averageValue);
// This also works.
    Console.WriteLine("The average of " + v1
                    + " and " + v2 + " is "
                    + Average(v1, v2));
  }
}

Notice first that I declare the method as public static double Average() — the double in front of its name indicates that the Average() method returns a double-precision value to the caller.

The Average() method applies the names d1 and d2 to the double values passed to it. This method creates a variable average to which it assigns the average of d1 and d2 and returns to the caller the value contained in average.

Warning

People sometimes use this careless but common shorthand: "The method returns average." Saying that average or any other variable is passed or returned anywhere is imprecise. In this case, the value contained within average is returned to the caller.

The call to Average() from the Test() method appears the same as any other method call; however, the double value returned by Average() via the return keyword is stored in the variable averageValue.

Note

A method that returns a value, such as Average(), cannot return to the caller by merely encountering the closed brace of the method. If it did, C# wouldn't know which value to return. You need a return statement.

Defining a method with no value

The declaration public static double Average(double, double) declares a method Average() that returns the average of its arguments as a double. (The number returned had better be the average of the input values or else someone has some serious explaining to do.)

Some methods don't need to return a value to the caller. An earlier method AverageAndDisplay() example displays the average of its input arguments but doesn't return that average to the caller. (That idea may not be a good one, but mine is not to question.) Rather than leave the return type blank, you declare a method such as AverageAndDisplay() this way:

public void AverageAndDisplay(double, double)

The keyword void, where the return type is normally used, means nontype. That is, the declaration void indicates that the AverageAndDisplay() method returns no value to the caller. (Regardless, every method declaration specifies a return type, even if it's void.)

Note

A void method returns no value. This definition doesn't mean that the method is empty or that it's used for medical or astronautical purposes; it simply refers to the initial keyword. By comparison, a method that returns a value is a nonvoid method.

A nonvoid method must pass control back to the caller by executing a return followed by the value to return to the caller. A void method has no value to return. A void method returns when it encounters a return with no value attached. Or, by default (if no return exists), a void method exits automatically when control reaches the closing brace of the method.

Consider this DisplayRatio() method:

public class Example
{
  public static void DisplayRatio(double numerator,
                                  double denominator)
  {
    // If the denominator is zero . . .
    if (denominator == 0.0)
    {
      //  . . .output an error message and . . .
      Console.WriteLine("The denominator of a ratio cannot be 0");
      //  . . .return to the caller.
      return;  // An early return due to the error
    }
    // This code is executed only if denominator is nonzero.
    double ratio = numerator / denominator;
    Console.WriteLine("The ratio of " + numerator
                    + " over " + denominator
                    + " is " + ratio);
  }  // If the denominator isn't zero, the method exits here.
}

The DisplayRatio() method checks whether the denominator value is zero:

  • If the value is zero: The program displays an error message and returns to the caller without attempting to calculate a ratio. Doing so would divide the numerator value by zero and cause a CPU processor fault, also known by the more descriptive name processor upchuck.

  • If the value is nonzero: The program displays the ratio. The closed brace immediately following WriteLine() is the closed brace of the method DisplayRatio() and therefore acts as the return point for the program.

If that were the only difference, it wouldn't be much to write home about. However, the second form of WriteLine() also provides a number of controls on the output format. I describe these format controls in Book I, Chapter 3.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.37.123