Recall that even the most complicated expressions involving binary operators can be divided into sub-expressions, each consisting of two operands and an operator; and remember how operator precedence and associativity rules determine to which operand each operator is applied. Along with the values passed through the various levels of this sub-expression hierarchy are also the types of these values, as illustrated in Figure 7.3.
Consequently, if we can somehow determine the type of any expression involving just two operands and one binary operator, we can also determine the type of any expression, irrespective of its number of operators and operands. This allows us to simplify our discussion considerably. We now only need to ask how does the C# compiler determine the type of an expression consisting of two operands of any primitive type and one of the following binary operators +, -, *, / and %? Let's attempt to get an answer for this important question.
To determine the type of a two-operand-one-operator expression, the C# compiler will go through a neatly designed algorithm, presented in Figure 7.4 using a flowchart.
A flowchart is a graphical diagram sometimes used by programmers to illustrate an algorithm. Instead of merely relying on text like pseudocode, a flowchart contains symbols to express how the algorithm is executed.
Flowcharts can contain numerous specialized symbols, but the four basic symbols presented in Figure 7.4 let you illustrate most algorithms.
Figure 7.5 provides a simple example of a flowchart. You start at the oval marked Begin and arrive at a decision symbol containing the condition "The person is a male." If this condition is true, Man will be printed; if the condition is false, Woman will be printed.
Figure 7.5. Man or woman?
If you can trace this flowchart, you can also trace much larger flowcharts; simply move through them symbol-by-symbol and arrow-by-arrow.
The type-determination algorithm in Figure 7.6 needs to know the types of the two operands involved, and depending on this specific combination of types, it will report the chosen type for the expression or report an error because two incompatible types were used.
To demonstrate how to use the flowchart in Figure 7.6, let's trace it with a couple of examples.
The first example contains an expression with the operand types short and double. We begin at the oval marked Begin and follow the arrow to the diamond, containing the condition "One of the operands is of type decimal." Because none of our types are of type decimal, this condition is false, so we follow the arrow marked false. We arrive at a new diamond with the condition "One of the operands is of type double." This is true, so we move along the arrow marked true and find ourselves in an action symbol with the text "Set the (sub) expression to be of type double." Lastly, we follow the arrow out and down to the oval marked End. The conclusion: An expression with operands of type short and double has a value of type double.
Now it's your turn. Try the following combinations of types:
decimal and short
double and decimal
short and ulong
byte and short
Let's have a look at the answers (the last three perhaps are surprising).
decimal and short results in an expression of type decimal.
double and decimal causes the compiler to report the following error:
"Operator cannot be applied to operands of type decimal and double".
This is caused by the inability of the compiler to implicitly convert a type double to a type decimal and a type decimal to a type double. Thus, if distance is of type decimal and speed is of type double, the following expression is invalid:
(distance / speed) Invalid
Fortunately, there is a simple remedy for this problem type cast speed to be of type decimal, resulting in the following line:
(distance / (decimal)speed) Valid
short and ulong triggers an error with the following comment:
"Operator is ambiguous on operand of type ulong combined with operand of type sbyte, short, int or long" .
Thus, the type ulong does not get along with any of the signed integer types (sbyte, short, int, and long) in the same expression. This is because none of the integer types can simultaneously accommodate the vast range of positive values found in ulong and the negative values of the signed integer types.
byte and short results in an expression of type int just like any expression with a combination of the integer types sbyte, byte, short, and ushort.,This is similar to the unary plus and minus operators returning the type int when applied to operands of type sbyte, byte, short, and ushort.
The algorithm depicted in Figure 7.6 does not attempt to illustrate how the actual algorithm inside the C# compiler is implemented. Figure 7.6 is only meant as an aid for us to determine the type of an expression.
Notice that no matter which of the five operators appear in the expression, the chosen type will remain unchanged.
The next section provides an overview of why metadata is a groundbreaking concept and gives you a peek at how it is accessed. In particular, you will see how we can check the answers found in the flowchart of Figure 7.6 with a relatively simple program.