[ Team LiB ] |
Recipe 3.11 Making Error-Free ExpressionsProblemA 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. SolutionThe 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. DiscussionParentheses 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:
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.
|
[ Team LiB ] |