Declaring a Method
This section expands on the explanation of declaring a method to include parameters or a return type. Listing 5.4 contains examples of these concepts, and Output 5.1 shows the results.
Listing 5.4: Declaring a Method
public class IntroducingMethods { public static void Main() { string firstName; string lastName; string fullName; string initials; Console.WriteLine("Hey you!"); firstName = GetUserInput("Enter your first name: "); lastName = GetUserInput("Enter your last name: "); fullName = GetFullName(firstName, lastName); initials = GetInitials(firstName, lastName); DisplayGreeting(fullName, initials); } static string GetUserInput(string prompt) { Console.Write(prompt); return Console.ReadLine() ?? string.Empty; } static string GetFullName( string firstName, string lastName) => $"{ firstName } { lastName }"; static void DisplayGreeting(string fullName, string initials) { Console.WriteLine( $"Hello { fullName }! Your initials are { initials }"); return; } static string GetInitials(string firstName, string lastName) { return $"{ firstName[0] }. { lastName[0] }."; } }
Output 5.1
Hey you! Enter your first name: Inigo Enter your last name: Montoya Hello Inigo Montoya! Your initials are I. M.
Listing 5.4 declares five methods. From Main() the code calls GetUserInput(), followed by a call to GetFullName() and GetInitials(). All of the last three methods return a value and take arguments. In addition, the listing calls DisplayGreeting(), which doesn’t return any data.
Formal Parameter Declaration
Consider the declarations of the DisplayGreeting(), GetFullName(), and the GetInitials() methods. The text that appears between the parentheses of a method declaration is the formal parameter list. (As we will see when we discuss generics, methods may also have a type parameter list. When it is clear from the context which kind of parameters we are discussing, we simply refer to them as parameters in a parameter list.) Each parameter in the parameter list includes the type of the parameter along with the parameter name. A comma separates each parameter in the list.
Behaviorally, most parameters are virtually identical to local variables, and the naming convention of parameters follows accordingly. Therefore, parameter names use camelCase. Also, it is not possible to declare a local variable (a variable declared inside a method) with the same name as a parameter of the containing method, because this would create two local variables of the same name.
Method Return Type Declaration
In addition to GetUserInput(), GetFullName(), and the GetInitials() methods requiring parameters to be specified, each of these methods includes a method return type. You can tell that a method returns a value because a data type appears immediately before the method name in the method declaration. Each of these method examples specifies a string return type. Unlike parameters, of which there can be any number, only one method return type is allowable.
As with GetUserInput() and GetInitials(), methods with a return type almost always2 contain one or more return statements that return control to the caller. A return statement consists of the return keyword followed by an expression that computes the value the method is returning. For example, the GetInitials() method’s return statement is return $"{ firstName[0] }. { lastName[0] }.";. The expression (an interpolated string in this case) following the return keyword must be compatible with the stated return type of the method.
If a method has a return type, the block of statements that makes up the body of the method must not have an unreachable end point. That is, there must be no way for control to “fall off the end” of a method without returning a value. Often the easiest way to ensure that this condition is met is to make the last statement of the method a return statement. However, return statements can appear in locations other than at the end of a method implementation. For example, an if or switch statement in a method implementation could include a return statement within it; see Listing 5.5 for an example.
Listing 5.5: A return Statement before the End of a Method
public class Program { // ... public static bool MyMethod() { string command = ObtainCommand(); switch(command) { case "quit": return false; // ... omitted, other cases default: return true; } } // ... }
(Note that a return statement transfers control out of the switch, so no break statement is required to prevent illegal fall-through in a switch section that ends with a return statement.)
In Listing 5.5, the last statement in the method is not a return statement; it is a switch statement. However, the compiler can deduce that every possible code path through the method results in a return, so that the end point of the method is not reachable. Thus, this method is legal even though it does not end with a return statement.
If particular code paths include unreachable statements following the return, the compiler will issue a warning that indicates the additional statements will never execute.
Though C# allows a method to have multiple return statements, code is generally more readable and easier to maintain if there is a single exit location rather than having multiple returns sprinkled through various code paths of the method.
Specifying void as a return type indicates that there is no return value from the method. As a result, a call to the method may not be assigned to a variable or used as a parameter type at the call site. A void method call may be used only as a statement. Furthermore, within the body of the method the return statement becomes optional, and when it is specified, there must be no value following the return keyword. For example, the return of Main() in Listing 5.4 is void, and there is no return statement within the method. However, DisplayGreeting() includes an (optional) return statement that is not followed by any returned result.
Although, technically, a method can have only one return type, the return type could be a tuple. As a result, starting with C# 7.0, it is possible to return multiple values packaged as a tuple using C# tuple syntax. For example, you could declare a GetName() method, as shown in Listing 5.6.
Listing 5.6: Returning Multiple Values Using a Tuple
public class Program { static string GetUserInput(string prompt) { Console.Write(prompt); return Console.ReadLine() ?? string.Empty; } static (string First, string Last) GetName() { string firstName, lastName; firstName = GetUserInput("Enter your first name: "); lastName = GetUserInput("Enter your last name: "); return (firstName, lastName); } static public void Main() { (string First, string Last) name = GetName(); Console.WriteLine($"Hello { name.First } { name.Last }!"); } }
Technically, we are still returning only one data type, a ValueTuple<string, string>. However, effectively, you can return any (preferably reasonable) number you like using each item within the tuple.
Expression Bodied Methods
To support the simplest of method declarations without the formality of a method body, C# 6.0 introduced expression bodied methods, which are declared using an expression rather than a full method body. Listing 5.4’s GetFullName() method provides an example of the expression bodied method:
static string GetFullName( string firstName, string lastName) =>
In place of the curly brackets typical of a method body, an expression bodied method uses the “goes to” operator (fully introduced in Chapter 13), for which the resulting data type must match the return type of the method. In other words, even though there is no explicit return statement in the expression bodied method implementation, it is still necessary that the return type from the expression match the method declaration’s return type.
Expression bodied methods are syntactic shortcuts to the fuller method body declaration. As such, their use should be limited to the simplest of method implementations—generally expressible on a single line.