Making Decisions in Code - Understanding C# Syntax - Beginning Object-Oriented Programming with C# (2012)

Beginning Object-Oriented Programming with C# (2012)

Part II

Understanding C# Syntax

Chapter 6

Making Decisions in Code

What you will learn in this chapter:

· Relational operators

· Comparing value types

· Comparing reference types

· The if and else keywords

· Cascading if statements

· Logical operators

· The switch, case, break, and default keywords

· Data validation

wrox.com code downloads for this chapter

You can find the wrox.com code downloads for this chapter at www.wrox.com/remtitle.cgi?isbn=9781118336922 on the Download Code tab. The code in the Chapter06 folder is individually named according to the names throughout the chapter.

If computers could not use data to make decisions, they would be little more than expensive boat anchors. Because it is hard to write a nontrivial program without some form of decision-making ability in it, some decision-making keywords were used in previous programs. However, what those keywords actually do was never explained. That changes by the end of this chapter.

This chapter also presents some thoughts on coding style. Coding style simply refers to the way you write program code. There are a myriad of coding styles. Because C# is not form-specific, you are free to use just about any coding style you wish. I have known brilliant programmers whose code is almost impossible to read or decipher. On the other hand, I've had mediocre students who write code that is a joy to read. True, beauty is in the eye of the beholder. However, experience has taught me that 80 percent of a program's development time is spent in testing, debugging, and maintaining the code, and only 20 percent in writing it. For that reason, anything you can do to make your code clearer and more easily understood is a good idea.

Relational Operators

Making a decision about anything involves comparing one thing against another. It's no different in a computer program. You learned in Chapter 3 that statements are built up from operators and operands. You used the math operators to illustrate how program statements are constructed from those basic building blocks. In this section you learn another set of operators that you have at your disposal: the relational operators. The relational operators are presented in Table 6.1.

Table 6.1 Relational Operators

image

Referring to Table 6.1, every example has one operand on the left side of the relational operator (15) and a second operand on the right side of the operator (20). Again, two operands are associated with the use of a relational operator, so all relational operators are binary operators. Therefore, combining two operands with a relational operator results in a relational expression.

Each relational expression ultimately resolves to a logic True or logic False state. In C#, logic True and logic False are expressed as boolean values. The result of a boolean expression resolves to the C# keywords true or false. Referring to Table 6.1, each example shown in the third column resolves to the state shown in the fourth column. In the table, literal values are used simply to make the relational expression more concrete. In programming situations, however, the operands in a relational expression are usually variables rather than literals.

Using Relational Operators—The if Statement

Now consider what you've learned about relational operators and put them to work in a C# if statement. Although you have seen several examples of the if statement in earlier chapters, how they work hasn't been explained. That's the purpose of this section.

The syntax form for an if statement is as follows:

if (expression1)

{

// Logic true: if statement block

}

expression1 appears between the opening and closing parentheses following the if keyword. Usually, expression1 represents a relational test to be performed. Referring to Table 6.1, the outcome of a relational expression is either logic True or logic False. The code that appears between the two curly braces is called the if statement block. If expression1 evaluates to logic True, then the if statement block is executed. If expression1 evaluates to logic False, program control skips over the if statement block and resumes program execution at the next statement following the closing curly brace of the if statement block. You can see how the program flows based on the outcome of expression1 in Figure 6.1.

Figure 6.1 The if statement block

image

Let's test your understanding of how an if statement works using a simple program that determines whether a number entered by the user is an odd or even number.

Try It Out: Testing for Odd or Even (Chapter06ProgramOddEven.zip)

To illustrate a simple use of an if statement, consider the program shown in Figure 6.2.

1. Create a new project as shown in Chapter 2 using the C# template.

2. Add two label objects, one textbox object and two buttons.

3. Set the objects' properties as you want. Figure 6.2 is one user interface that you might use.

4. Add the button click event code, as shown in Listing 6-1.

Figure 6.2 The if program for odd or even Numbers

image

The user enters a number, and based upon its value, the program tells whether the number is odd or even. The program should also check that the user entered a valid sequence of digit characters for the number. The letter O is just below the zero digit character (0) and sometimes people in a hurry press the wrong key.

Listing 6-1: The btn_Click event code (frmMain.cs)

private void btnCalc_Click(object sender, EventArgs e)

{

bool flag;

int val;

string output = "Number is even";

// Convert from text to number

flag = int.TryParse(txtNumber.Text, out val);

if (flag == false)

{

MessageBox.Show("Not a number. Re-enter.");

txtNumber.Clear();

txtNumber.Focus();

return;

}

// See if odd or even

if (val % 2 == 1)

{

output = "Number is odd";

}

// Show result

lblOutput.Text = output;

}

How It Works

The code that does all the work is found in the button click event method (refer to Listing 6-1).

The btnCalc_Click() method begins by defining several working variables. Note how you initialized the string variable named output with the string literal Number is even. You then used the TryParse() method to convert the text entered by the user into a numeric value.

How the TryParse( ) Method Works

You have briefly studied the TryParse()method in earlier chapters. However, because it is such a useful method, a more complete discussion using some of the terms you learned in the last chapter is presented here.

The TryParse() method is normally used to convert data from a string format to a numeric format. In this example, the TryParse() method is part of the int object type, as shown by the int.TryParse() method call in Listing 6-1. The TryParse() method requires two arguments: 1) the string to be converted, and 2) the variable that will hold the numeric value if the data conversion is successful.

The keyword out in the second argument is necessary and must precede the numeric variable name. The data type of the variable (such as val) must match the flavor of TryParse() you use (such as int .TryParse() in this example). You can think of the out keyword as serving two purposes.

First, the out keyword tells C# that it's okay to use this variable even though it has not been initialized to any known value. (You should always assume that an uninitialized C# variable contains random junk. You will write better code making this assumption.)

Second, the out keyword also tells C# that it is the lvalue being passed to the method, not the rvalue. Recall that, by default, arguments are passed by making a copy of the rvalue of the data item. With TryParse(), it is the lvalue that gets passed. Using the Bucket Analogy, this means that the TryParse() method knows where the bucket for val is located in memory. Because this flavor of TryParse() is a method for the int object type, it also knows the size of the bucket. Given that TryPrase() knows where the data resides in memory and its type, TryPrase() has everything it needs to permanently change the value of the variable being passed to it in its second argument.

TryParse() takes the string of characters from the first argument and tries to format those characters into a numeric data value. If one of the characters is inconsistent with a numeric digit character, the conversion fails. TryParse() returns false to the caller and moves the value 0 into the rvalue for the variable. TryParse() has no problems setting val to 0 because it knows where val resides in memory and how many bytes it takes.

If the TryParse() method fails (for example, because the user entered a nondigit character), the variable flag is logic False. The if statement,

if (flag == false)

checks the state of flag using the equality relational operator (==). If flag is false because TryParse() failed to convert the user's input into an integer value, the expression in the if statement is logic True. That is, it is true that flag is false. (Think about it.) The code then executes the ifstatement block code, which displays a message about the error, sets the program focus back into the textbox object, and then returns program control back to the caller.

To Clear() or Not to Clear()

The Clear() method simply removes any characters that currently exist in the Text property of a textbox object. The program statement in the if statement block is controversial:

txtNumber.Clear();

This is because some people think you should allow the user to see his mistake after he has viewed the message you displayed by the call to MessageBox.Show(), whereas the other school of thought says that you should clear out the error in preparation for the entry of the new, hopefully correct, value. There is no “right” answer here. You can choose either approach, as long as you use it consistently. Users like consistency, so pick a style and stick with it.

Assuming the user entered a proper numeric value, that number is assigned into val by the TryParse() method. The next if statement uses the modulus operator to find the remainder of the user's number:

if (val % 2 == 1)

{

output = "Number is odd";

}

Obviously, if you divided a number by two and there is a remainder, the number must be odd. Therefore if

val % 2

yields a result of 1, then the if test appears as if it were written like this:

if (1 == 1)

The result of this test is obviously logic True. Therefore, if the number is odd, its remainder is 1 and the if test expression is true. That means that the code for the if statement block is executed and output is assigned the string literal Number is odd.

You can cause the program to ignore or execute the statements in the if statement block based upon the state of the if expression. Whenever the expression is true, the statement block is executed. Whenever the expression is false, the statement block is skipped. As simple as it is to understand, the if statement is the major building block that enables computers to have the processing capabilities they do.

The absolute best way to understand how an if statement alters the flow of a program is to single-step through the code using different values for val. To do this, place your cursor on the statement that reads

flag = int.TryParse(txtNumber.Text, out val);

and press the F9 key to set a debugging breakpoint. Now run the program. When the program reaches the breakpoint, single-step the program by repeatedly pressing the F10 key. (You can also use the F11 key, which is even better because it causes the program to single-step through any methods that you wrote as well.)

“use of unassigned local variable”

Sometimes, when you use the if statement, you may get the following error message:

use of unassigned local variable i

You can illustrate this error with the following code snippet:

int i;

// Some additional lines of code

if (x == MAX)

{

i = 10;

}

i *= i;

Visual Studio issues the error message for the last program line, stating that you are trying to use variable i before a value has been assigned to it. The reason Visual Studio acts this way is that it does not know at compile time whether the if statement resolves to logic True to assign the value 10 into i. If the if expression is logic False, variable i is uninitialized and has no known value assigned to it. You can fix this problem by simply initializing variable i with a default value. Most of the time, you do this when the variable is defined. Simply change the definition of i to this:

int i = 0;

This sets the rvalue of i to 0 and Visual Studio is happy because i now has a known value regardless of the outcome of the if test expression.

The if-else Statement

If you look closely at the code in Listing 6-1, you can notice that the output string is set to the literal Number is even at the moment the string is defined. Therefore, the string is changed only when the number is odd. If you think about it, what you actually want to do is set the output string based upon whether the number is odd or even. The program works, but it doesn't reflect what you want to do.

The if-else statement is the perfect solution to clarify your code. The syntax for the if-else statement is the following:

if (expression1)

{

// Logic true: if statement block

}

else

{

// Logic false: else statement block

}

With the if-else statement, if (expression1) evaluates to logic True, the if statement block is executed. If expression1 is logic false, the statements within the curly braces following the else keyword are executed. The group of statements within the curly braces following the else keyword is called the else statement block. The program flow for the if-else statement is shown in Figure 6.3.

Figure 6.3 if-else statement block

image

As you can see in Figure 6.3, the if-else statement creates a “this-or-that” kind of decision. That is, the decision produces one of two mutually exclusive outcomes. In the following Try It Out you revise the program shown in Listing 6-1 to use an if-else construct.

Try It Out: if-else Statement (Chapter06ProgramOddEven.zip)

To illustrate a simple use of an if-else statement block, revise the program shown in Figure 6.2.

1. Load the program for the simple if test you wrote earlier in this chapter.

2. Change the definition of output to

string output;

3. Replace the second if statement with this:

// See if odd or even

if (val % 2 == 1)

{

output = "Number is odd";

}

else

{

output = "Number is even";

}

How It Works

Most programmers would agree that this form is easier to understand because the outcomes are clearly stated in one place. With the first form of the program, you had to look at a different section of the program (the definition and initialization of output) to see what the string was when the number was even.

What if you were writing this logic test for a toaster where memory limitations restrict you to a few kilobytes of code space? Could you rewrite the code to save a few bytes? What if you initialize the string as follows:

string output = "Number is ";

and then recode the if-else block as

// See if odd or even

if (val % 2 == 1)

{

output += "odd";

}

else

{

output += "even";

}

In the if statement test expression, if the result is logic true, then the word "odd" is appended to "Number is" for output. If the test evaluates to logic false, then "even" is appended to output. The rest of the code behaves the same as it did earlier.

Don't even think about reading further until you are convinced that the program still behaves as before, but you can understand how a few bytes might be saved.

Shorthand for Simple if-else: The Ternary Operator

Because if-else statements are so common in programs, C# has a special operator designed specifically to replace a simple (that is, one statement) if-else statement block. C# uses the ternary operator to replace simple if-else statements. The syntax for the ternary operator is

(expression1) ? trueExpression : falseExpression

The statement works by first evaluating expression1. If expression1 evaluates to logic True, then trueExpression is evaluated. If expression1 is logic False, then falseExpression is evaluated. The logic flow can be seen in Figure 6.4.

Figure 6.4 The Ternary Operator

image

In the following Try It Out you rewrite the if-else version of the odd-even program, replacing the if-else statement blocks with the ternary operator.

Try It Out: Ternary Operator: (Chapter06ProgramOddEven.zip)

To illustrate a simple use of the ternary operator, revise the program shown in Figure 6.2 as follows:

1. Load the program for the simple if-else test that uses the code from Figure 6.2.

2. Replace the second if-else statement block with this:

// See if odd or even

output = (val % 2 == 0) ? "Number is even" : "Number is odd";

Now run the program to see if it behaves as it did before.

How It Works

Using the modified code, the variable output is assigned the string literal Number is even if val % 2 resolved to 0. Otherwise, if the modulus operator yields a value of 1, output is assigned Number is odd. You should set a breakpoint on the line and single-step through the code to see that it behaves as expected.

Although the ternary operator is a shorthand form of a simple if-else statement, I'm not a big fan of its use. There are three reasons why. First, it takes most programmers more time to understand a ternary expression than a simple if-else statement block. If you return to a piece of code during a debugging session a month after you originally wrote it and it uses a ternary operator, it will take you longer to understand its intent than it should. Second, I just have this nagging feeling that programmers who use it do so to show they can use it. I come away after reading a ternary statement feeling like the programmer is saying: “Watch this, Billy-Bob.” Third, if either of the test statement blocks need multiple statements to provide the correct results, you have to write some fairly ugly code to get the correct results. Given these reasons, if the code is clearer with a simple if-else statement, why use the ternary operator?

Style Considerations for if and if-else Statements

C# is a format-insensitive language. This means that you could write the if-else statement as a single line, like this:

if (val % 2 == 1){output = "Number is odd";}else{output = "Number is even";}

Visual Studio would process the statements just fine. However, most programmers would find the preceding line much more difficult to read without the line breaks and indentation. Such formatting issues are factors in the style you use to write your code. You do have some choices.

For example, when an if or an else statement block consists of a single statement, you don't need the curly braces. That is, you could write this code:

if (val % 2 == 1)

{

output = "Number is odd";

}

else

{

output = "Number is even";

}

as this:

if (val % 2 == 1)

output = "Number is odd";

else

output = "Number is even";

The program would execute exactly as before. However, when the if statement block contains multiple statements, you must use the curly braces. For example, suppose you wrote the following code:

if (val % 2 == 1)

{

output = "Number is odd";

oddCounter++;

}

Your intent is to count the number of odd numbers using oddCounter. If you write the same code without curly braces:

if (val % 2 == 1)

output = "Number is odd";

oddCounter++;

your intent may be the same, but without the curly braces what you actually have is this:

if (val % 2 == 1)

{

output = "Number is odd";

}

oddCounter++;

The reason the code functions differently is that without the curly braces, the if statement controls only a single statement. The result of this error is that oddCounter is always incremented regardless of the value of the number val—probably not what you wanted to do.

So the style question becomes, “Should you use curly braces when the if statement block contains only one statement?” The answer is yes. Always use curly braces. First, always using curly braces promotes consistency in your coding. Anytime you use an if statement, curly braces follow. Second, often what starts out as a single statement in the if statement block ends up with multiple statements after testing and debugging. You'll have to add the braces anyway, so you might as well add them from the get-go. Third, most programmers think the curly braces make the statement blocks stand out more and make them easier to read. The lesson learned here is always to use curly braces, even when the if statement block only contains one statement.

Another style consideration is where to place the curly braces. For example, because C# doesn't care about statement formatting, you could write the previous code as follows:

if (val % 2 == 1){

output = "Number is odd";

} else {

output = "Number is even";

}

Note that the required curly braces are still where they need to be, but that their location is different. This K&R style was made popular by the C programming book written by Brian Kernighan and Dennis Ritchie in the 1970s (The C Programming Language, Prentice Hall, 1978). One reason this style is popular is that it uses two fewer screen lines, meaning that you can make better use of your display's nano-acres. Seeing more source code without scrolling is usually a good thing.

So which style should you use? Well, obviously C# doesn't care; although Visual Studio places the braces on separate lines by default. If you work on a programming team, such style considerations should be a team (or company) decision. If you just write code for yourself, it's your choice. Personally, I really like the K&R style mainly because C was the first language I enjoyed using, and most C programmers used the K&R style. Also, fewer students do something silly like

if (x == 2);

x++;

when they use braces and the K&R style. What this little snippet actually is becomes more obvious when braces are added:

if (x == 2){

;

}

x++;

This is probably not what the programmer meant to do. Always using braces helps reduce this type of silliness.

There is no “correct” style. However, whatever style you select, use it consistently.

Nested if Statements

Note

By default, Visual Studio prefers placing the braces on a separate line. That is, if you try to put the brace on the same line as the if expression, it automatically reformats the statement with the curly brace moved to the next line. However, after the reformatting, if you move the brace back to the previous line, Visual Studio enables you to keep it there.

You can change the default behavior of the editor to enable you to keep thebrace on the same line by using the Tools → Customize → Text Editor → C# → Formatting → New Lines menu sequence and removing the check mark from the Place Open Brace on New Line for Control Blocks option. You can scroll down and do the same thing for the else part of the statement block. (Make sure you have a check mark in the Show All Settings check box.)

You can have if statements within if statements. For example, suppose you own a bar and ladies get drinks at half price on Tuesdays. You might have code like this:

decimal price = FULLPRICE;

if (age >= 21)

{

if (gender == "Female")

{

price *= .5M;

}

} else {

MessageBox.Show("Illegal minor.");

}

In this example, you take the drink price (why is the symbolic constant FULLPRICE used here?) and multiply it by .5 if the customer is a female age 21 or older. If the gender is male, the full price prevails. Because you have an if statement contained within the if statement block, this is called anested if statement. The else statement block may also contain nested if statements.

You may ask, “What's with the letter M after the .5?” Recall from the discussion of data types that several value types have suffixes that can be appended to them to make it clear what the data type is. In this case, because you deal with the price of a drink (that is, with money), you want to use thedecimal data type. If you don't place the M after the .5, C# assumes the literal .5 is a double data type by default. C# views this as an error because you try to assign a double into a decimal data type and issues an appropriate error message. Also, the decimal data type is smart enough to keep track of only the pennies on a price, whereas a double may have many more than two decimal places.

Finally, you might ask why multiply by .5 instead of dividing by 2. After all, the results are the same, so why not use the conventional divide-by-2 approach? There are two reasons. First, multiplying by .5 is a fairly common programming idiom for dividing a number in half. Also, the .5 does convey the “half price” concept of the discount. (Although a comment on the line wouldn't hurt.) Second, division is the slowest of the four basic math operations. Multiplication is slightly faster than division. Although the speed improvement in this example would be unnoticeable, such would not be the case if the operation were performed in a tight loop doing tens of thousands of iterations. Also, you needed to see an example of this idiom in case you run across it while looking at someone else's code.

RDC

You probably noticed that the code does work on forming a discounted price for ladies, but it does not consider that the discount is only to apply on Tuesdays. How would you fix that problem?

Sometimes you need to do a search that involves multiple values, any of which might be correct depending upon some variable. For example, suppose you have a variable that tells what day of the week it is and you want to perform various tasks based on that value. You might write the following code:

if (day == 1)

{

doMondayStuff();

}

if (day == 2)

{

doTuesdayStuff();

}

if (day == 3)

{

doWednesdayStuff();

}

if (day == 4)

{

doThursdayStuff();

}

if (day == 5)

{

doFridayStuff();

}

if (day == 6)

{

doSaturdayStuff();

}

if (day == 7)

{

doSundayStuff();

}

Perhaps the method doTuesdayStuff() adjusts the price for ladies' drinks.

Regardless, the code depicted is another example of RDC (Really Dumb Code). The reason is that the structure shown here often causes unnecessary code to be executed. For example, suppose you want to do Monday's tasks (day == 1). The first if test is logic True, so the code executesdoMondayStuff(). That's the good news. The bad news is that when program control returns after executing the doMondayStuff() method, it then performs six unnecessary if tests on variable day. The tests are unnecessary because you already know that day's value is 1. Therefore, the code executes six unnecessary if tests even though you know they can't be logic true.

When you realize what makes your code RDC, you usually perform a flat-forehead slap and ask yourself why you didn't see it in the first place. Not to worry … most good programmers have well-earned flat foreheads! The important thing in becoming a good programmer is to move to more complex bugs and not repeat the same old ones over and over.

Believe it or not, I actually saw this kind of RDC code in a production environment, but was even worse because it was written to handle 31 days of the month issues. The code meant that if it were the first day of the month, there would be 30 unnecessary if tests executed.

The next section shows you how to get rid of this kind of RDC. Next, let's see how to improve the code and avoid all the potentially unnecessary if tests.

Try It Out: Getting Rid of RDC: (Chapter06ProgramOddEven.zip)

Again you can reuse the code you wrote for the odd-even program shown in Listing 6-1 for this example.

1. Load the program code.

2. Delete the code in the button click event after the comment line:

// See if odd or even

and replace it with the following code. (In more realistic code, each if block would likely call a method to do something useful.)

int day;

day = val;

if (day == 1)

{

day += 1; // Simulate doing Monday's stuff

} else

if (day == 2)

{

day += 2; // Simulate doing Tuesday's stuff

} else

if (day == 3)

{

day += 3; // Simulate doing Wednesday's stuff

} else

if (day == 4)

{

day += 4; // Simulate doing Thursday's stuff

} else

if (day == 5)

{

day += 5; // Simulate doing Friday's stuff

} else

if (day == 6)

{

day += 6; // Now Saturday's stuff

} else

if (day == 7)

{

day += 7; // Lastly Sunday's stuff

}

Now compile and run the program.

How It Works

Code like the preceding code is called a cascading if statement. As a general rule, cascading if statements perform different tasks based upon the state of a particular variable. In this code snippet, it appears that different methods are called based up on the value of the variable day. If you follow the logic carefully, you see that the six unnecessary if tests are no longer executed when the first test is logic True. (Use the debugger to prove this is the case.) The reason the unnecessary code is now skipped is that when an if statement becomes logic True there is no reason to evaluate any subsequent if expressions.

Quite often cascading if statements are a bit clumsy and difficult to read, simply because there are so many if statements and curly braces that things can appear a little confusing. (Imagine how ugly this code would look for 31 days in a month!) The other problem is that if you use proper indentation with each if expression, your code gets pushed farther and farther to the right of the display screen. Eventually, this can force you to use horizontal scrolling, which most programmers dislike. You see a more effective way to write cascading if statements in the section that discusses the switch keyword.

Logical Operators

Sometimes it is necessary to make complex decisions based upon the state of two or more variables. For example, to determine if a year is a leap year, you apply the following test:

If the year can be evenly divided by 4, but not by 100, it is a leap year. The exception occurs if the year is evenly divisible by 400. If so, it is a leap year.

If you try to code this as a series of simple if statements, the code gets messy. It's actually easier to code the algorithm if you can use a combination of logical operators in a single statement block. The C# conditional logical operators are shown in Table 6.2.

Table 6.2 Conditional Logic Operators

ITEM

MEANING

EXAMPLE

Logical &&

Logical AND

x && y

Logical ||

Logical OR

x || y

Logical !

Logical NOT

!x

Because these are logical operators, each one resolves to a true or false expression. You can see these relationships by using truth tables. Table 6.3 shows the truth table for the logical AND operator.

Table 6.3 Logical AND Truth Table

Expression1

Expression2

Expression1 && Expression2

True

True

True

True

False

False

False

True

False

False

False

False

The last column in Table 6.2 shows that the logical AND truth table is constructed using two expressions with the && operator. Table 6.3 shows the outcome for all possible values for those two expressions and the impact on the logical AND result. For example, if Expression1 resolves to logic trueand Expression2 resolves to logic false, the result is logic false. This is because a logical AND requires both expressions to be True for the result to be True. All other combinations yield a result that is logic False.

Table 6.4 shows the truth table for the logical OR operator. As you can see in the table, the result of a logical OR statement is True if either or both expressions is logic true. Only when both expressions are false is the result false.

Table 6.4 Logical OR Truth Table

Expression1

Expression2

Expression1 || Expression2

True

True

True

True

False

True

False

True

True

False

False

False

Finally, Table 6.5 shows the truth table for the logical NOT operator. Unlike the other operators, logical NOT is a unary operator, which means it uses only a single expression. Therefore, Table 6.5 is rather simple.

Table 6.5 Logical NOT Truth Table

Expression1

!Expression

True

False

False

True

As you can see from Table 6.5, the logical NOT operator simply “flips” the current logic state of the expression.

Using the Logical Operators

Now revisit the algorithm to determine if a year is a leap year. The algorithm is, “If the year can be evenly divided by 4, but not by 100, it is a leap year. The exception occurs if the year is evenly divisible by 400. If so, it is a leap year.”

You might code the algorithm like this:

if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)

{

return 1; // It is a leap year

}

else

{

return 0; // Nope

}

If you examine the algorithm and then the code, you should convince yourself that the single if statement does implement the algorithm correctly.

Note that this leap year code returns either one or zero, where most leap year methods (that is, both .NET and Java) return a boolean of true or false. Why is it coded differently? With this version, you can do something like

days = DAYSINFEBRUARY + isLeapYear(myYear);

where DAYSINFEBRUARY is a constant that assumes the value of 28. If the method returns a bool, you have no choice but to use an if statement like

if (isLeapYear(myYear))

{

days = DAYSINFEBRUARY + 1;

}

The version that returns an integer is more flexible. Clearly, there's some bright people who disagree. There is almost always more than one way to solve a given programming problem.

However, things are not always as simple as a leap-year calculation. You might get something like this:

if (x + y < b && z - y == c || z * 5 - b == d)

{

answer = x * y * z;

}

The preceding if statement mixes logical, conditional, and math operators together. So how do you know what to do to resolve such a complex if statement? (The first thing you do is fire the idiot who wrote the code. Program statements rarely need to be this complex, and debugging it would be a nightmare.) As you learned in Chapter 4, the precedence order determines the order in which operators are evaluated. However, because you have added new operators in this chapter, you need to expand the precedence table to include the new operators. The expanded precedence table is presented in Table 6.6. (Although you haven't studied all the operators shown in Table 6.6, they are presented here for completeness.)

Table 6.6 Operator Precedence

OPERATOR

TYPE

. ++ -- new

Primary

!

Unary

* / %

Math

+ -

Math

< > <= >=

Relational

== !=

Relational

&& ||

Logical

?:

Ternary

= *= /= %= += -=

Assignment

,

Comma

If you're not happy with the order in which a group of expressions are evaluated, you can always force something to be evaluated out of its natural precedence by containing the expression within parentheses. (You may want to write this page number on the inside of the back cover page of this book because you will likely need to refer to this table at some later time.)

Associativity

How is precedence resolved when an expression contains two operators with the same precedence level? Precedence-level ties are broken by the associativity rules for operators. As a general rule operators are left-associative, which means the expressions are evaluated from left to right in the statement. Again, if you need to override the precedence and associativity rules, you can use parentheses to force your preferred order. Consider the following code:

x = 5 + 6 * 10;

The answer is 65. This is because multiplication has higher precedence than addition, so the sequence becomes:

x = 5 + 6 * 10;

x = 5 + 60;

x = 65;

If you actually need the answer to be 110, then you would use parentheses:

x = (5 + 6) * 10;

x = 11 * 10;

x = 110;

The switch Statement

You learned earlier that you could replace the RDC with a cascading if statement when you want to test for a specific day, as in the days-of-the-week example:

if (day == 1)

{

doMondayStuff();

} else

if (day == 2)

{

doTuesdayStuff();

} else

if (day == 3)

{

do WednesdayStuff();

}

// The rest of the days are similar to above

Although this code works just fine, it's often difficult to read cascading if statements. That's because you still must match up the proper if statement with its associated day value. Secondly, if the cascade is fairly long, the code starts to scroll off to the right. Programmers hate horizontal scrolling because it wastes time. They would rather read all the code without scrolling. Third, code like this may also force you to do vertical scrolling. The switch statement offers one way to resolve some of these drawbacks.

The syntax for the switch statement is as follows:

switch (expression1)

{

case 1:

// Statements for case 1

break;

case 1:

// Statements for case 2

break;

case 2:

// Statements for case 3

break;

default:

// Statements for default

break;

}

With a switch statement, the value of expression1 determines which case statement block is executed. You can think of each case statement block as starting with the colon (:) following the case keyword and extending to the end of the associated break keyword.

If expression1 does not match any case statement, the default expression is evaluated. Unlike an if statement block, no parentheses are necessary for a case statement block. The break statements are necessary to prevent program control from “falling through” to the next case statement. Thebreak statement causes program execution to resume with the first statement after the switch statement block's closing curly brace.

Next let's write a simple program that uses a switch statement block to make complex decisions instead of a bunch of nested if-else blocks. You will find the switch version much easier to read and it is slightly more efficient.

Try It Out: Using a switch Statement: (Chapter06ProgramSwitch.zip)

Load the code from the Chapter06ProgramSwitch.zip file or use the code shown in Listing 6-2. Only the btnCalc click event code is shown in the listing because the remaining code is virtually the same as all the other programs you've written.

Listing 6-2: The btn_Click event code (frmMain.cs)

private void btnCalc_Click(object sender, EventArgs e)

{

bool flag;

int myDay;

string msg = "Today is "; // Don't duplicate it 7 times

// Make text into int

flag = int.TryParse(txtDay.Text, out myDay);

if (flag == false)

{

MessageBox.Show("Numeric only, 1 thru 7");

txtDay.Focus(); // Send 'em back to try again

return;

}

switch (myDay)

{

case 1:

lblResult.Text = msg + "Monday";

break;

case 2:

lblResult.Text = msg + "Tuesday";

break;

case 3:

lblResult.Text = msg + "Wednesday";

break;

case 4:

lblResult.Text = msg + "Thursday";

break;

case 5:

lblResult.Text = msg + "Friday";

break;

case 6:

lblResult.Text = msg + "Saturday";

break;

case 7:

lblResult.Text = msg + "Sunday";

break;

default:

lblResult.Text = "You should never get here";

break;

}

}

In a more realistic situation, each of the case statements would do something a little more interesting. However, this simple code can service your purpose nicely.

How It Works

If you study this code fragment, you can discover that it works like a cascading if statement; only it's quite a bit easier to read. Also, you don't need to do horizontal scrolling to see all the code at once.

It is the value of day that determines which case statement block is executed. The break statement in each case block sends program control to the first statement following the closing curly brace of the switch statement block. Therefore, it is the break statement that enables the switch to perform like a cascading if statement, thus avoiding unnecessary if tests.

If variable day does not have a value between 1 and 7, the default case is executed. (You are not required to use a default case, but it's a good idea to include one in each switch statement.) This situation displays a simple error message. It's fairly common to have the default case call a logging method that records the nature of the error. Often, the error log file is just a simple text file that can be read with any word processing program. Perhaps the ErrorLog() method records the value of day in the error log file so that the person in charge to support the program can ask the user over the phone to read the last line in the error log file. The support person would, therefore, have some idea of the value for day that produced the error.

Summary

This chapter presented the basics to make decisions in a program. The basic building block of computer decision making is the simple if statement. From that, you learned about the if-else and switch statements and showed when it may be appropriate to use them. You also read some thoughts on programming style and the issues involved in deciding which coding style to use. Even though C# doesn't care about your coding style, other people who may read your code do. In all cases, try to make your code as easy to read as possible.

Exercises

You can find the answers to the following exercises in Appendix A.

1. Suppose a movie theater charges half price for children 12 or under or adults age 65 or older. Write an if statement that accounts for this pricing policy.

2. What are the important style considerations when you're writing if-else statements?

3. When is it appropriate to use a cascading if statement?

4. What errors do you see in the following code snippet?

if (x = y);

{

price =* .06;

}

5. Suppose a small pizza sells for $6, a medium is a dollar more, and a large is a dollar more than a medium. Assuming that the variable size stores the customer's selection, how would you write the code to determine the final price?

6. Write a program that accepts a year as input and then calls a method named IsLeapYear(int year) to then display the proper number of days in February for the year entered by the user.

What You Have Learned in This Chapter

TOPIC

KEY POINTS

if

How to use the if statement.

TryParse()

Greater detail on how to use this conversion method.

if-else

Use of the if-else statement block.

Cascading if's

How to use cascading if statements while avoiding redundant if tests.

? :

The ternary operator.

Logical operators

What are the logical operators and their truth tables.

switch

How to use the switch, case, break, and default statements.