Selecting Code Paths Conditionally

Selecting Code Paths Conditionally

The first type of control statements we’ll cover are the selection statements. Selection statements give you the ability to execute specific sections of your code based on conditions you specify. Using selection statements, you can choose to execute code if a specific condition is true, if a value is less than a predefined value, or if a specific menu selection has been made.

The if Statement

The if statement is used to test an expression and execute one or more statements if the expression evaluates as true, as follows:

if(condition)
    controlled-statement;

The if statement can be thought of as guarding or controlling execution of a statement, only allowing execution if a specific expression is evaluated as true.

To conditionally execute multiple statements, a statement block must be created by enclosing the controlled statements within curly braces ({}), as shown here:

if(name.Length > 0)
{
    Console.WriteLine("Your name is:");
    Console.WriteLine(name);
}

The expression tested by the if statement must have a Boolean value. Unlike C or C++, C# doesn’t implicitly convert a numeric value of 0 to false, with all other values implicitly converted to true. The following code is valid C or C++ code, but it won’t be allowed by the C# compiler:

// Valid C or C++, not valid C#
if(name.Length)
{
    Console.WriteLine(name);
}

Although popular in C and C++, implicit conversions for Boolean tests are often a source of programming errors. C# requires that you provide a Boolean expression, as in the previous examples, where name.Length was explicitly compared to a value of 0.

The if statement can be extended to execute one of two statements by adding an else statement, as shown here:

if(name.Length > 0)
    Console.WriteLine(name);
else
    Console.WriteLine("Please enter your name.");

An else statement is always associated with a specific if statement. Although C# doesn’t have an else-if statement as is found in Microsoft Visual Basic, an else and an if can be combined to provide the same effect:

if(name.Length > 0)
    Console.WriteLine(name);
else if(retries < 3)
    Console.WriteLine("Please enter your name.");
else
    DisplayHelp();

When using nested if and else statements, watch out for cases in which indentation provides misleading information about which statements will be executed for a particular expression, as shown here:

if(name == "Jen")
    DisplayName();
else if(ageChecked == true)
    if(age < 40)
        DisplayAge();
else
    age = GetAge(); 

In this example, the indentation implies that GetAge is invoked if name isn’t equal to Jen when ageChecked is false. However, the else statement always binds to the most recent if statement that isn’t already bound to an else statement. Therefore, GetAge is actually associated with the test of age < 40, which is quite different from the implied ordering.

To avoid problems of this sort, it’s a good idea to use statement blocks and curly braces to make your intent clear. Rewriting the preceding example using statement blocks, as shown here, makes it possible for the C# compiler to generate the code as you intended:

if(name == "Jen")
{
    DisplayName();
}
else if(ageChecked == true)
{
    if(age < 40)
    {
        DisplayAge();
    }
}
else
{
    age = GetAge(); 
}

In C and C++, it’s common practice to reuse variable names by declaring variables with the same names in nested blocks. To prevent a host of common programming errors, C# doesn’t permit you to redeclare a variable name in a nested block. For example, the following code contains a nested statement block that declares the string variable name:

static void Main(string[] args)
{
    string name = Console.ReadLine();
    if(name.Length == 0)
    {
        // Not allowed in C#; can't declare
        // a variable with the same name.
        string name = Console.ReadLine();
        Console.WriteLine(name);
    }
}

The name variable in the nested statement block clashes with a variable with an enclosing scope, and the compiler will reject the variable redeclaration as an error.

The rule preventing nested declarations using the same variable name applies even if the variable in the outer block is declared after the inner block. Therefore, this code also is rejected by the compiler:

static void Main(string[] args)
{
    if(args.Length == 0)
    {
        string name = Console.ReadLine();
        Console.WriteLine(name);
    }
    // Redeclaration not allowed in C#.
    string name = "Rene";
}
The switch Statement

The if and else statements are useful when you’re conditionally selecting from a small group of options. However, these statements become unwieldy when you must choose from among a large number of cases. The switch statement is used to select among multiple choices, offering a more elegant syntax for situations that would otherwise require cascading if statements, as shown here:

string name = Console.ReadLine();

    

switch(name)
{
    case "Mickey":
        Console.WriteLine("Hello Mickey");
        break;

    case "Mackenzie":
        Console.WriteLine("Hello Mackenzie");
        break;

    default:
        Console.WriteLine("Hello World");
        break;
}

The switch statement uses the following three keywords:

  • switch  The switch clause introduces the expression that’s used to determine the branch value for the statement. This expression can be a variable, as in the preceding example, a method call, or any other expression.

  • case  One or more case labels are used to specify a constant value that’s compared to the switch expression. The constant value included in the case label must have a type that’s compatible with the switch expression. Each case label is usually followed by one or more optional statements that are executed if the value in the case label matches the switch expression. Later in this section, you’ll learn how to combine case labels to execute the same statements for multiple conditions.

  • default  The default clause is used to define statements that will be executed if none of the case labels have values that match the switch expression.

In addition, each case label must terminate its group of controlled statements by using a jump expression, such as the break statement in the preceding example. The C# jump statements are discussed later in this chapter, in the section “Using Jump Statements to Transfer Control.”

When multiple case levels require the same work to be done, the case labels can be combined, like this:

switch(name)
{
    case "Ali":
    case "Mackenzie":
        PlaySoccer();
        break;

    
    

    default:
        break;
}

The practice of “falling through” case labels is common C and C++ idiom but isn’t allowed in C#. If there are any statements under a case label, the compiler won’t allow you to write code that simply falls through to the next case. For example, this code is legal in C and C++, but not in C#:

switch(name)
{
    case "Ali":
        LookForSocks(); // Implicit fall-through
                        // Not allowed in C#
    case "Mackenzie":
        PlaySoccer();
        break;
    
    

    default:
        break;
}

The reason for barring this type of construction is that it’s a common source of programming errors. If you need this behavior, you can use a goto statement to explicitly transfer control to another case label, as shown here:

switch(name)
{
    case "Ali":
        LookForSocks();
        goto case "Mackenzie"; // Explicit fall-through OK.

    case "Mackenzie":
        PlaySoccer();
        break;
    
    

    default:
        break;
}

Unlike in C and C++, a case label in C# can specify a string value, with each case label specifying a separate string value that will be compared with the switch expression. This feature makes the C# switch statement usable in situations where a C or C++ programmer would be forced to use nested if and else statements.

When the statements executed for a particular case label require the use of temporary variables, you must create a statement block that scopes the lifetime and visibility of the variables, as shown here:

switch(name)
{
    case "Ali":
    {
        int sockCount = GetCurrentSockCount();
        if(sockCount < 2)
            LookForSocks();
        goto case "Mackenzie";
    }

    case "Mackenzie":
        PlaySoccer();
        break;
    
    

    default:
        break;
}

If you have case clauses that require multiple statements, it’s a good idea to break out the statements into individual methods, as shown in the following code. This practice helps make your code easier to read and maintain by reducing the size and complexity of the switch statement.

switch(name)
{
    case "Ali":
        HandleAliSoccer();
        break;

    case "Mackenzie":
        HandleMackenzieSoccer();
        break;
    
    

    default:
        break;
}


Part III: Programming Windows Forms