All of the code used in the lessons so far has been completely linear. The program follows a series of steps in order with no deviation.
For example, a sales program could multiply a unit price by quantity desired, add several items’ values, multiply to get sales tax and shipping costs, and calculate a grand total.
So far there’s been no way to perform different steps under different circumstances. For example, the sales program couldn’t charge different prices for different quantities purchased or waive shipping charges for orders over $100. It couldn’t even check quantities to see if they make sense. So far a clever customer could order –1,000 items to get a huge credit!
In this lesson you learn how a program can make decisions. You learn how the program can take different actions based on user inputs and other circumstances.
Programs often need to decide between two or more courses of action. For example:
The basic idea is the same in all of these cases. The program examines a value and takes one of several different actions depending on the value.
The following sections describe the different statements that C# provides for making these kinds of decisions.
The if
statement examines a condition and takes action only if the condition is true. The basic syntax for the if
statement is:
if (condition) statement;
Here condition
is some boolean expression that evaluates to either true or false, and statement
is a statement that should be executed if condition
is true.
For example, suppose you’re writing an order entry program and shipping should be $5 for orders under $100 and free for orders of at least $100. Suppose also that the program has already calculated the value total
. The following code shows how the program might handle this:
decimal shipping = 5.00M; // Default shipping cost.
if (total >= 100) shipping = 0; // Shipping is free if total >= 100.
The code starts by setting the variable shipping
to $5. Then if the previously calculated value total
is at least $100, the program sets shipping
to $0.
If total
is less than $100, the statement following the if
statement is not executed and shipping
keeps its original value of $5.
If you want to execute more than one statement when condition
is true, place the statements inside braces as in the following code:
decimal shipping = 5.00M; // Default shipping cost.
if (total >= 100)
{
shipping = 0; // Shipping is free if total >= 100.
giveFreeGift = true; // Give a free gift if total >= 100.
}
You can place as many statements as you like inside the braces, and they are all executed if condition
is true.
The previous example set shipping
to a default value and then changed it if total
was at least $100. Another way to think about this problem is to imagine taking one of two actions depending on total
’s value. If total
is less than $100, the program should set shipping
to $5. Otherwise the program should set shipping
to $0.
The if-else
construct lets a program follow this approach, taking one of two actions depending on some condition.
The syntax for if-else
is:
if (condition)
statementsIfTrue;
else
statementsIfFalse;
If condition
is true, the first block statementsIfTrue
executes. Otherwise (if condition
is false) the second block statementsIfFalse
executes.
Using the else
keyword, the preceding code could be rewritten like this:
decimal shipping;
if (total < 100)
shipping = 5M; // Shipping is $5 if total < 100.
else
shipping = 0M; // Shipping is free if total >= 100.
You can use braces to make either the if
or else
part of the if-else
statement execute more than one command.
The if-else
construct performs one of two actions depending on whether the condition is true or false. Sometimes a program needs to check several conditions to decide what to do.
For example, suppose an order entry program calculates shipping charges depending on the total purchase amount according to this schedule:
You can make a program perform each of these tests one after another by making a second if
statement be the else
part of a first if
statement. The following code shows how you can calculate shipping according to the preceding schedule:
decimal shipping;
if (total < 20)
{
shipping = 5M;
}
else if (total < 50)
{
shipping = 7.5M;
}
else if (total < 75)
{
shipping = 10M;
}
else
{
shipping = 0M;
}
When the program encounters a cascading series of if
statements, it executes each in turn until it finds one with a true condition. It then skips the rest because they are all part of the current if
statement’s else
block.
For example, consider the previous code and suppose total
is $60. The code evaluates the first condition and decides that (total < 20)
is false, so it does not execute the first code block.
The program skips to the else
statement and executes the next if
test. The program decides that (total < 50)
is also not true, so it skips to this if
statement’s else
block.
The program executes the third if
test and finds that (total < 75)
is true so it executes the statement shipping = 10M
.
Because the program found an if
statement with a true condition, it skips the following else
statement, so it passes over any if
statements that follow without evaluating their conditions.
Another common arrangement of if
statements nests one within another. The inner if
statement is executed only if the first statement’s condition allows the program to reach it.
For example, suppose you charge customers 5 percent state sales tax. If a customer lives within your county, you also charge a county transportation tax. Finally, if the customer also lives within city limits, you charge a city sales tax. (Taxes where I live are at least this confusing.)
The following code performs these checks, where the variables inCounty
and inCity
indicate whether the customer lives within the county and city:
if (inCounty)
{
if (inCity)
{
salesTaxRate = 0.09M;
}
else
{
salesTaxRate = 0.07M;
}
}
else
{
salesTaxRate = 0.05M;
}
You can nest if
statements as deeply as you like, although at some point the code gets hard to read.
The switch
statement provides an easy-to-read equivalent to a series of cascading if
statements that compares one value to a series of other values.
The syntax of the switch
statement is:
switch (testValue)
{
case (value1):
statements1;
break;
case (value2):
statements2;
break;
…
default:
statementsDefault;
break;
}
Here testValue
is the value that you are testing. The values value1
, value2
, and so on are the values to which you are comparing testValue. The statements1
, statements2
, and so on are the blocks of statements that you want to execute for each case. The other pieces (switch
, case
, break
, and default
) are keywords that you must type as they appear here.
If you include the optional default
section, its statements execute if no other case
applies. Actually the case
statements are optional, too, although it would be strange to not use any.
Note that a case
’s code block doesn’t need to include any statements other than break
. You can use that to make the code take no action when a particular case
occurs.
For example, suppose you build a form where the user selects a hotel from a ComboBox
. The program uses that selection to initialize an enumerated variable named hotelChoice
. The following code sets the lodgingPrice
variable depending on which hotel the user selected:
decimal lodgingPrice;
switch (hotelChoice)
{
case HotelChoice.LuxuryLodge:
lodgingPrice = 45;
break;
case HotelChoice.HamiltonArms:
lodgingPrice = 80;
break;
case HotelChoice.InvernessInn:
lodgingPrice = 165;
break;
default:
MessageBox.Show("Please select a hotel");
lodgingPrice = 0;
break;
}
The case
statements check for the three expected choices and sets lodgingPrice
to the appropriate value. If the user doesn’t select any hotel, the default
section’s code displays a message box and sets lodgingPrice
to 0 to indicate a problem.
A switch
statement is most robust (less prone to bugs and crashes) if its cases can handle every possible comparison value. That makes them work very well with enumerated types because you can list every possible value. In contrast, you can’t include a case
statement for every possible integer value (unless you include several billion lines of code), so case
statements can’t check every possible integer value.
Even if the case
statements check every possible value in an enumeration, it’s a good practice to include a default
section just in case another value sneaks into the code. For example, a bug in the code could convert an integer into an enumeration value that doesn’t exist, or you could later add a new value to the enumeration and forget to add a corresponding case
statement. In those cases, the default statement can catch the bug, take some default action, and possibly warn you that something is wrong.
When you use a switch
statement with other data types, be sure to consider unexpected values, particularly if the user typed in the value. For example, don’t assume the user will always enter a valid string. Allowing the user to select a string from a ComboBox
is safer, but you should still include a default
statement.
In this Try It, you build the Order Form program shown in Figure 18.1. The program uses a cascading series of if
statements to calculate shipping cost based on the subtotal.
In this lesson, you:
if-else
statements to calculate the shipping cost based on the subtotal as in the following code:// Calculate shipping cost.
decimal shipping;
if (subtotal < 20)
{
shipping = 5;
}
else if (subtotal < 50)
{
shipping = 7.5m;
}
else if (subtotal < 75)
{
shipping = 10;
}
else
{
shipping = 0;
}
When the user clicks the Calculate button, first check each ListBox
’s SelectedIndex
property. If any SelectedIndex
is less than zero (indicating the user didn’t make a choice), display an error message and use the return
keyword to stop calculating.
If the user made a choice in all of the ListBox
es, create a variable total
to hold the total cost. Use three switch
statements to add the appropriate amounts to total
and display the result. (Hint: Add a default
statement to each switch
statement to catch unexpected selections, even though none should occur in this program. Then add a new hotel to the ListBox
and see what happens if you select it.)
The basic idea is to check whether the document has been modified before doing anything that will lose the changes, such as starting a new document, opening another file, or exiting the program.
RichTextBox
’s Modified
property to see if the document has unsaved changes.RichTextBox
control’s Modified
property to false
to indicate that there are no unsaved changes at that time.Hint: Use a local variable named shouldContinue
to decide whether the operation should continue.
To fix this, give the form a FormClosing
event handler. When the form is about to close, it raises this event. If you set the event’s e.Cancel
parameter to true
, the form cancels the close and remains open. Add code to this event handler to protect unsaved changes.
Now that the FormClosing
event handler is protecting against lost changes, you don’t need to perform the same checks in the Exit menu item’s event handler. Make that event handler simply call the Close
method and let the FormClosing
event handler do the rest.
Label
s for each square: two to let the user select the square for X or O, and one to show which player has taken the square.
Modify the program to make the following changes (which should make the program much smaller):
Label
s so there’s only one Label
per square.Label
’s Tag
property to indicate its row and column. For example, set the Tag
property for the upper-left Label
to “0, 0.”Click
event handler for all of the Label
s.sender
parameter into the Label
that raised the Click
event.Label
’s Tag
property to see which entry in the Board
array to set. (Hint: Use ToString
to convert the Tag
property into a string
.)Label
(instead of saying “O’s turn”).Label
. After the game is over, ignore any click events until the user starts a new game.PictureBox
). When the program starts, give the PictureBox
a random position on the form and random X and Y velocities. When a Timer
ticks, use the velocities to calculate the PictureBox
’s new position. If the position makes the PictureBox
move beyond one of the form’s edges, move it back onto the form and reverse the corresponding velocity.// The SoundPlayer.
private System.Media.SoundPlayer BoingSound =
new System.Media.SoundPlayer(Properties.Resources.boing);
BoingSound.Play()
to play the sound when necessary.PictureBox
displaying a picture of a castle or some other target on the form.PictureBox
display an image of an explosion.PictureBox
) from the laser cannon (an image in another PictureBox
). (Hint: To know when the user presses Space, catch the form’s KeyDown
event and see if e.KeyCode == Keys.Space
.)UfoX
, UfoY
, UfoVx
, and UfoVy
to track the UFO’s position and velocity. Use similar variables for the laser bolt.Timer
fires, update the positions of the UFO and the bolt.Label
at the top of the form.Copy that program and make it more challenging by making these changes.
Copy the program and modify it so the user has only 10 laser bolts. Represent each with a PictureBox
visible on the form and keep track of the number of bolts remaining. When the user fires a bolt, use a switch
statement to hide the next bolt PictureBox
.
When all of the bolts are used, display a label on top of the game that shows the user’s final score and play a triumphant fanfare.
Array.Sort
to sort the arrays.