Operators
Although most of C#’s operators should be
familiar to C and C++ developers, this section discusses the most
important ones for the benefit of new programmers and Visual Basic
converts, as well as to shed light on some of the changes
introduced with C#.
C# supports the operators listed in the following
table, although four (sizeof,
*, ->, and
&) are only available in unsafe code
(code that bypasses C#’s type safety checking), which is discussed
in Chapter 11, “Memory Management and
Pointers”:
One of the biggest pitfalls to watch out for when
using C# operators is that, like other C-style languages, C# uses
different operators for assignment =,
and comparison ==. For instance, the
following statement means let x equal
three:
If you now want to compare x to a value, you need to use the double equals sign
==:
Fortunately, C#’s strict type-safety rules prevent
the very common C error where assignment is performed instead of
comparison in logical statements. This means that in C# the
following statement will generate a compiler error:
Visual Basic programmers who are accustomed to
using the ampersand (&) character to
concatenate strings will have to make an adjustment. In C#, the
plus sign (+) is used instead for
concatenation, whereas the & symbol
denotes a bitwise AND between two
different integer values. The symbol, |,
allows you to perform a bitwise OR
between two integers. Visual Basic programmers also might not
recognize the modulus (%) arithmetic
operator. This returns the remainder after division, so for example
x%5returns 2
if x is equal to 7.
You will use few pointers in C#; therefore, you
will use few indirection operators. More specifically, the only
place you will use them is within blocks of unsafe code, because
that’s the only place in C# where pointers are allowed. Pointers
and unsafe code are discussed in Chapter 11, “Memory
Management and Pointers.”
Operator Shortcuts
The following table shows the full list of
shortcut assignment operators available in C#.
You may be wondering why there are two examples
each for the ++ increment and the
-- decrement operators. Placing the
operator before the expression is known as
a prefix, and placing the operator
after the expression is known as a
postfix, and there is a difference in the
way they behave.
The increment and decrement operators can act both
as whole expressions and within expressions. When used by
themselves the effect of both the prefix and postfix versions is
identical and corresponds to the statement x =
x + 1. When used within larger expressions, the prefix
operator will increment the value of x
before the expression is evaluated; in
other words, x is incremented and the
new value is used in the expression. In contrast, the postfix
operator increments the value of x
after the expression is evaluated - the
expression is evaluated using the original value of x. The following example uses the increment operator
(++) as an example to demonstrate the
difference between the prefix and postfix behavior:
The first if condition
evaluates to true, because x is incremented from 5
to 6 before the
expression is evaluated. The condition in the second if statement is false,
however, because x is incremented to
7 only after the entire expression has
been evaluated (while x = 6).
The prefix and postfix operators --x and x-- behave in the
same way, but decrement rather than increment the operand.
The other shortcut operators, such as += and -=, require two
operands, and are used to modify the value of the first operand by
performing an arithmetic, logical, or bitwise operation on it. For
example, the next two lines are equivalent:
The Ternary Operator
The ternary operator (?:) is a shorthand form of the if...else construction. It gets its name from the
fact that it involves three operands. It allows you to evaluate a
condition, returning one value if that condition is true, or
another value if it is false. The syntax is:
Here, condition is the
Boolean expression to be evaluated, true_value is the value that will be returned if
condition is true, and false_value is the value that will be returned
otherwise.
When used sparingly, the ternary operator can add a
dash of terseness to your programs. It is especially handy for
providing one of a couple of arguments to a function that is being
invoked. You can use it to quickly convert a Boolean value to a
string value of true or false. It is also handy for displaying the correct
singular or plural form of a word, for example:
This code displays 1
man if x is equal to one but will
display the correct plural form for any other number. Note,
however, that if your output needs to be localized to different
languages, you will have to write more sophisticated routines to
take account of the different grammatical rules of different
languages.
The checked and unchecked Operators
Consider the following code:
The byte data type can
only hold values in the range zero to 255, so incrementing the
value of b causes an overflow. How the
CLR handles this depends on a number of issues, including compiler
options, so whenever there’s a risk of an unintentional overflow,
you need some way of making sure that you get the result you
want.
To do this, C# provides the checked and unchecked
operators. If you mark a block of code as checked, the CLR will enforce overflow checking, and
throw an OverflowException if an
overflow occurs. Let’s change the code to include the checked operator:
When you try to run this code, you will get an
error message like this:
|
|
Tip |
You can enforce overflow checking for all
unmarked code in your program by specifying the /checked compiler option.
|
If you want to suppress overflow checking, you can
mark the code as unchecked:
In this case, no exception will be raised, but you
will lose data - because the byte type
can’t hold a value of 256, the overflowing bits will be discarded,
and your b variable will hold a value of
zero.
Note that unchecked
is the default behavior. The only time you are likely to need to
explicitly use the unchecked keyword is
if you need a few unchecked lines of code inside a larger block
that you have explicitly marked as checked.
The is Operator
The is operator
allows you to check whether an object is compatible with a specific
type. For example, to check whether a variable is compatible with
the object type:
|
|
Tip |
The phrase “is compatible” means that an
object is either of that type or is derived from that type.
|
int, like all C# data
types, inherits from object; therefore
the expression i is object will evaluate
to true in this case, and the appropriate message will be
displayed.
The as Operator
The as operator is
used to perform explicit type conversions of reference types. If
the type being converted is compatible with the specified type,
conversion is performed successfully. However, if the types are
incompatible, the as operator returns
the value null. As shown in the
following code, attempting to convert an object reference to a string will return null
if the object reference does not
actually refer to a string instance:
The as operator
allows you to perform a safe type conversion in a single step
without the need to first test the type using the is operator and then perform the conversion.
The sizeof Operator
You can determine the size (in bytes)
required on the stack by a value type using the sizeof operator:
This will display the number 4, because an int is 4
bytes long.
Notice that you can only use the sizeof operator in unsafe code. Chapter
11 looks at unsafe code in more detail.
The typeof Operator
The typeof
operator returns a System.Type object
representing a specified type. For example, typeof(string) will return a Type object representing the System.String type. This is useful when you want to
use reflection to find out information about an object dynamically.
Chapter 12, “Reflection,” looks at
reflection.
Nullable Types and Operators
Looking at the Boolean type, you have a true
or false value that you can assign to this type. Though, what if
you wanted to define the value of the type as undefined? This is
where using nullable types can have a distinct value to your
applications. If you use nullable types in your programs, you must
always consider the effect a null value
can have when used in conjunction with the various operators.
Usually, when using a unary or binary operator with nullable types,
the result will be null if one or both
of the operands is null. For
example:
However, when comparing nullable types, if only one
of the operands is null, the comparison
will always equate to false. This means
that you cannot assume a condition is true just because its opposite is false, as often happens in programs using
non-nullable types. For example:
|
|
Tip |
The possibility of a null value means that you cannot freely combine
nullable and non-nullable types in an expression. This is discussed
in the “Type Conversions” section later in this
chapter.
|
The Null Coalescing Operator
The null coalescing operator (??) provides a shorthand mechanism to cater to the
possibility of null values when working
with nullable and reference types. The operator is placed between
two operands - the first operand must be a nullable type or
reference type, and the second operand must be of the same type as
the first or of a type that is implicitly convertible to the type
of the first operand. The null coalescing operator evaluates as
follows: if the first operand is not null, then the overall expression has the value of
the first operand. However, if the first operand is null, the overall expression has the value of the
second operand. For example:
If the second operand cannot be implicitly
converted to the type of the first operand, a compile-time error is
generated.
Operator Precedence
The following table shows the order of
precedence of the C# operators. The operators at the top of the
table are those with the highest precedence (that is, the ones
evaluated first in an expression containing multiple
operators).
|
|
Tip |
In complex expressions, you should avoid
relying on operator precedence to produce the correct result. Using
parentheses to specify the order in which you want operators
applied clarifies your code and avoids potential confusion.
|