Recipe 3.11 Making Error-Free Expressions
Problem
A
complex expression in
your code is returning incorrect results. For example, if you wanted
to find the average area given two circles, you might write the
following expression:
double radius1 = 2;
double radius2 = 4;
double aveArea = .5 * Math.PI * Math.Pow(radius1, 2) + Math.PI *
Math.Pow(radius2, 2);
However, the result is always incorrect.
Complex mathematical and Boolean equations in your code can easily
become the source of bugs. You need to write bug-free equations,
while at the same time making them easier to read.
Solution
The solution is quite simple: use
parentheses to explicitly define the order of operations that will
take place in your equation. To fix the expression presented in the
Problem section, rewrite it as follows:
double radius1 = 2;
double radius2 = 4;
double aveArea = .5 * (Math.PI * Math.Pow(radius1, 2) + Math.PI *
Math.Pow(radius2, 2));
Notice the addition of the parentheses; these parentheses cause the
area of the two circles to be calculated and added together first.
Then the total area is multiplied by .5. This is
the behavior we are looking for. An additional benefit is that the
expression can become easier to read as the parentheses provide clear
distinction of what part of the expression is to be evaluated first.
This technique works equally well with Boolean equations.
Discussion
Parentheses are key to writing maintainable and bug-free equations.
Not only is your intention clearly spelled out, but you also override
any operator precedence rules that you might not have taken into
account. In fact, the only way to override operator precedence is to
use parentheses (you can use temporary variables to hold partial
results, which aids in readability, but can increase the size of the
IL code). Consider the following equation:
int x = 1 * 2 - -50 / 4 + 220 << 1;
Console.WriteLine("x = " + x);
The value 468 is displayed for this equation.
This is the same equation written with parentheses:
int y = ((1 * 2) - ((-50) / 4) + 220) << 1;
Console.WriteLine("y = " + y);
The same value (468) is also displayed for this
equation. Notice how it is much easier to read and understand how
this equation works when parentheses are used. It is possible to get
carried away with the use of parentheses in an equation:
int z = ((((1 * 2) - ((-50) / 4)) + 220) << (1));
Console.WriteLine("z = " + z);
This equation also evaluates to 468, but due to
the overuse of parentheses, you can get lost determining where one
set of parentheses begins and where it ends. You should try to
balance your placement of parentheses in strategic locations to
prevent oversaturating your equation with parentheses.
Another place where you can get into trouble with operator precedence
is when using the ternary operator (?:).
The ternary operator is defined as follows:
boolean-expression ? true-case-expression : false-case-expression
Each type of expression used by this operator is defined as follows:
- boolean-expression
-
This expression must evaluate to a Boolean value or to a value whose
type has an implicit conversion to bool or one
that has a true operator. Depending on the outcome
of this expression, either the
true-case-expression or the
false-case-expression will be executed.
- true-case-expression
-
This expression is evaluated when the
boolean-expression evaluates to
true.
- false-case-expression
-
This expression is evaluated when the
boolean-expression evaluates to
false.
Either the true-case-expression or the
false-case-expression will be evaluated;
never both.
The ternary operator is able to compact several lines of an
if-else statement into a single expression that
can fit easily on a single line. This ternary statement is also
usable inline with a statement or another expression. The following
code example shows the use of the ternary operator inline with an
expression:
byte x = 8 + ((foo == 1) ? 4 : 2);
By examining the order of operator precedence, we see that the ==
operator is evaluated first and then the ternary operator. Depending
on the result of the Boolean expression foo
== 1, the ternary operator will
produce either the value 4 or
2. This value is then added to
8 and assigned to the variable
x.
This operator is considered to have
right-associative properties, similar to the
assignment operators. Because of this, you can get into trouble using
ternary expressions as expressions within other ternary expressions.
Consider the following code:
// foo currently equals 1
// Assume that all methods will always return a Boolean true, except for Method3,
// which always returns a Boolean false
Console.WriteLine(Method1( ) ? Method2( ) : Method3( ) ? Method4( ) : Method5( ));
Which methods will be called? If you started evaluating this
expression from the left, your expression would essentially look like
the following:
Console.WriteLine((Method1( ) ? Method2( ) : Method3( )) ? Method4( ) : Method5( ));
Notice the extra highlighted parentheses added to clarify how the
expression will be evaluated in this manner. The answer that the
methods Method1, Method2, and
Method4 will be called is wrong. The ternary
operators are evaluated from right to left, not left to right, as are
most other common operators. The correct answer is that only
Method1 and Method2 would be
called. Extra highlighted parentheses have been added to this
expression, in order to clarify how it is evaluated:
Console.WriteLine(Method1( ) ? Method2( ) : (Method3( ) ? Method4( ) : Method5( )));
This technique will cause Method1 and
Method2 to be called in that order. If any of
these methods produced side effects, the application might produce
unexpected results.
 |
If you must use nested ternary expressions, make liberal use of
parentheses around each ternary expression to clearly specify your
intentions.
|
|
|