CHAPTER 4
Operators

C# provides an extensive set of operators that give the programmer detailed control over the construction and evaluation of expressions. Most of C#’s operators fall into the following categories: arithmetic, bitwise, relational, and logical. These operators are examined in this chapter. Also discussed are the assignment operator and the ? operator. C# also defines several other operators that handle specialized situations, such as array indexing, member access, and the lambda operator. These special operators are examined later in this book, when the features to which they apply are described.

Arithmetic Operators

C# defines the following arithmetic operators:

Image

The operators +, –, *, and / all work in the expected way. These can be applied to any builtin numeric data type.

Although the actions of arithmetic operators are well known to all readers, a few special situations warrant some explanation. First, remember that when / is applied to an integer, any remainder will be truncated; for example, 10/3 will equal 3 in integer division. You can obtain the remainder of this division by using the modulus operator, %. The % is also referred to as the remainder operator. It yields the remainder of an integer division. For example, 10 % 3 is 1. In C#, the % can be applied to both integer and floating-point types. Thus, 10.0 % 3.0 is also 1. (This differs from C/C++, which allow modulus operations only on integer types.) The following program demonstrates the modulus operator.

// Demonstrate the % operator.

using System;

class ModDemo {
  static void Main() {
    int iresult, irem;
    double dresult, drem;

    iresult = 10 / 3;
    irem = 10 % 3;

    dresult = 10.0 / 3.0;
    drem = 10.0 % 3.0;

    Console.WriteLine("Result and remainder of 10 / 3: " +
                        iresult + " " + irem);
    Console.WriteLine("Result and remainder of 10.0 / 3.0: " +
                        dresult + " " + drem);
  }
}

The output from the program is shown here:

Result and remainder of 10 / 3: 3 1
Result and remainder of 10.0 / 3.0: 3.33333333333333 1

As you can see, the % yields a remainder of 1 for both integer and floating-point operations.

Increment and Decrement

Introduced in Chapter 2, the ++ and the – – are the increment and decrement operators. As you will see, they have some special properties that make them quite interesting. Let’s begin by reviewing precisely what the increment and decrement operators do.

The increment operator adds 1 to its operand, and the decrement operator subtracts 1. Therefore,

x = x + 1;

is the same as

x++;

and

x = x - 1;

is the same as

x--;

Understand, however, that in the increment or decrement forms, x is evaluated only once, not twice. This can improve efficiency in some cases.

Both the increment and decrement operators can either precede (prefix) or follow (postfix) the operand. For example

x = x + 1;

can be written as

++x; // prefix form

or as

x++; // postfix form

In the foregoing example, there is no difference whether the increment is applied as a prefix or a postfix. However, when an increment or decrement is used as part of a larger expression, there is an important difference. When an increment or decrement operator precedes its operand, the result of the operation is the value of the operand after the increment. If the operator follows its operand, the result of the operation is the value of the operand before the increment. Consider the following:

x = 10;
y = ++x;

In this case, y will be set to 11. This is because x is first incremented and then its value is returned. However, if the code is written as

x = 10;
y = x++;

then y will be set to 10. In this case, the value of x is first obtained, x is incremented, and then the original value of x is returned. In both cases, x is still set to 11. The difference is what is returned by the operation.

There are significant advantages in being able to control when the increment or decrement operation takes place. Consider the following program, which generates a series of numbers:

// Demonstrate the difference between prefix and
// postfix forms of ++.

using System;

class PrePostDemo {
  static void Main() {
    int x, y;
    int i;

    x = 1;
    y = 0;
    Console.WriteLine("Series generated using y = y + x++;");
    for(i = 0; i < 10; i++) {
      y = y + x++; // postfix ++

      Console.WriteLine(y + " ");
    }
     Console.WriteLine();

     x = 1;
     y = 0;
     Console.WriteLine("Series generated using y = y + ++x;");
     for(i = 0; i < 10; i++) {

       y = y + ++x; // prefix ++

       Console.WriteLine(y + " ");
     }
     Console.WriteLine();
   }
}

The output is shown here:

Series generated using y = y + x++;
1
3
6
10
15
21
28
36
45
55

Series generated using y = y + ++x;
2
5
9
14
20
27
35
44
54
65

As the output confirms, the statement

y = y + x++;

adds the current values of x and y, and assigns this result back to y. The value of x is incremented after its value has been obtained. However, the statement

y = y + ++x;

obtains the value of x, increments x, and then adds that value to the current value of y. This result is assigned to y. As the output shows, simply changing ++x to x++ changes the number series quite substantially.

One other point about the preceding example: Don’t let expressions like

y + ++x

intimidate you. Although having two operators back-to-back is a bit unsettling at first glance, the compiler keeps it all straight. Just remember, this expression simply adds the value of y to the value of x incremented.

Relational and Logical Operators

In the terms relational operator and logical operator, relational refers to the relationships that values can have with one another, and logical refers to the ways in which true and false values can be connected together. Since the relational operators produce true or false results, they often work with the logical operators. For this reason they will be discussed together here.

The relational operators are as follows:

Image

The logical operators are shown next:

Image

The outcome of the relational and logical operators is a bool value.

In general, objects can be compared for equality or inequality using == and !=. However, the comparison operators, <, >, <=, or >=, can be applied only to those types that support an ordering relationship. Therefore, all of the relational operators can be applied to all numeric types. However, values of type bool can only be compared for equality or inequality since the true and false values are not ordered. For example, true > false has no meaning in C#.

For the logical operators, the operands must be of type bool, and the result of a logical operation is of type bool. The logical operators, &, |, ^, and !, support the basic logical operations AND, OR, XOR, and NOT, according to the following truth table:

Image

As the table shows, the outcome of an exclusive OR operation is true when one and only one operand is true.

Here is a program that demonstrates several of the relational and logical operators:

// Demonstrate the relational and logical operators.

using System;

class RelLogOps {
  static void Main() {
    int i, j;
    bool b1, b2;

    i = 10;
    j = 11;
    if(i<j) Console.WriteLine("i<j");
    if(i <= j) Console.WriteLine("i <= j");
    if(i != j) Console.WriteLine("i != j");
    if(i == j) Console.WriteLine("this won't execute");
    if(i >= j) Console.WriteLine("this won't execute");
    if(i > j) Console.WriteLine("this won't execute");

    b1 = true;
    b2 = false;
    if(b1&b2) Console.WriteLine("this won't execute");
    if(!(b1&b2)) Console.WriteLine("!(b1&b2) is true");
    if(b1 | b2) Console.WriteLine("b1 | b2 is true");
    if(b1 ^ b2) Console.WriteLine("b1 ^ b2 is true");
  }
}

The output from the program is shown here:

i < j
i <= j
i != j
!(b1 & b2) is true
b1 | b2 is true
b1 ^ b2 is true

The logical operators provided by C# perform the most commonly used logical operations. However, several other operations are defined by the rules for formal logic. These other logical operations can be constructed using the logical operators supported by C#. Thus, C# supplies a set of logical operators sufficient to construct any other logical operation. For example, another logical operation is implication. Implication is a binary operation in which the outcome is false only when the left operand is true and the right operand is false. (The implication operation reflects the idea that true cannot imply false.) Thus, the truth table for the implication operator is shown here:

Image

The implication operation can be constructed using a combination of the ! and the | operator, as shown here:

!p | q

The following program demonstrates this implementation:

// Create an implication operator in C#.

using System;

class Implication {
  static void Main() {
     bool p=false, q=false;
     int i, j;

     for(i = 0; i < 2; i++) {
       for(j = 0; j < 2; j++) {
         if(i==0) p = true;
         if(i==1) p = false;
         if(j==0) q = true;
         if(j==1) q = false;

         Console.WriteLine("p is " + p + ", q is " + q);
         if(!p | q) Console.WriteLine(p + " implies " + q +
                    " is " + true);
         Console.WriteLine();
      }
    }
  }
}

The output is shown here:

p is True, q is True
True implies True is True

p is True, q is False
p is False, q is True
False implies True is True

p is False, q is False
False implies False is True

Short-Circuit Logical Operators

C# supplies special short-circuit versions of its AND and OR logical operators that can be used to produce more efficient code. To understand why, consider the following. In an AND operation, if the first operand is false, then the outcome is false no matter what value the second operand has. In an OR operation, if the first operand is true, then the outcome of the operation is true no matter what the value of the second operand. Thus, in these two cases there is no need to evaluate the second operand. By not evaluating the second operand, time is saved and more efficient code is produced.

The short-circuit AND operator is && and the short-circuit OR operator is ||. As described earlier, their normal counterparts are & and |. The only difference between the normal and short-circuit versions is that the normal operands will always evaluate each operand, but short-circuit versions will evaluate the second operand only when necessary.

Here is a program that demonstrates the short-circuit AND operator. The program determines if the value in d is a factor of n. It does this by performing a modulus operation. If the remainder of n / d is zero, then d is a factor. However, since the modulus operation involves a division, the short-circuit form of the AND is used to prevent a divide-by-zero error.

// Demonstrate the short-circuit operators.

using System;

class SCops {
  static void Main() {
    int n, d;

    n = 10;
    d = 2;
    if(d != 0 && (n % d) == 0)
      Console.WriteLine(d + " is a factor of " + n);

    d = 0; // now, set d to zero

    // Since d is zero, the second operand is not evaluated.
    if(d != 0 && (n % d) == 0)
       Console.WriteLine(d + " is a factor of " + n);

    // Now, try the same thing without the short-circuit operator.
    // This will cause a divide-by-zero error.
    if(d != 0 & (n % d) == 0)
      Console.WriteLine(d + " is a factor of " + n);
  }
}

To prevent a divide-by-zero error, the if statement first checks to see if d is equal to zero. If it is, then the short-circuit AND stops at that point and does not perform the modulus division. Thus, in the first test, d is 2 and the modulus operation is performed. The second test fails because d is set to zero, and the modulus operation is skipped, avoiding a divide-by-zero error. Finally, the normal AND operator is tried. This causes both operands to be evaluated, which leads to a runtime error when the division-by-zero occurs.

Since the short-circuit operators are, in some cases, more efficient than their normal counterparts, you might be wondering why C# still offers the normal AND and OR operators. The answer is that in some cases you will want both operands of an AND or OR operation to be evaluated because of the side effects produced. Consider the following:

// Side effects can be important.

using System;

class SideEffects {
  static void Main() {
     int i;
     bool someCondition = false;

     i = 0;

     // Here, i is still incremented even though the if statement fails.
     if(someCondition & (++i < 100))
        Console.WriteLine("this won't be displayed");
     Console.WriteLine("if statement executed: " + i); // displays 1

     // In this case, i is not incremented because the short-circuit
     // operator skips the increment.
     if(someCondition && (++i < 100))
       Console.WriteLine("this won't be displayed");
     Console.WriteLine("if statement executed: " + i); // still 1 !!
  }
}

First, notice that the bool variable someCondition is initialized to false. Next, examine each if statement. As the comments indicate, in the first if statement, i is incremented despite the fact that someCondition is false. When the & is used, as it is in the first if statement, the expression on the right side of the & is evaluated no matter what value the expression on the left has. However, in the second if statement, the short-circuit operator is used. In this case, the variable i is not incremented because the left operand, someCondition, is false, which causes the expression on the right to be skipped. The lesson here is that if your code expects the right-hand operand of an AND or OR operation to be evaluated, then you must use C#’s non-short-circuit forms for these operations.

One other point: The short-circuit AND is also known as the conditional AND, and the short-circuit OR is also called the conditional OR.

The Assignment Operator

The assignment operator is the single equal sign, =. The assignment operator works in C# much as it does in other computer languages. It has this general form:

var-name = expression;

Here, the type of var-name must be compatible with the type of expression.

The assignment operator does have one interesting attribute that you may not be familiar with: It allows you to create a chain of assignments. For example, consider this fragment:

int x, y, z;

x = y = z = 100; // set x, y, and z to 100

This fragment sets the variables x, y, and z to 100 using a single statement. This works because the = is an operator that yields the assigned value. Thus, the value of z = 100 is 100, which is then assigned to y, which in turn is assigned to x. Using a “chain of assignment” is an easy way to set a group of variables to a common value.

Compound Assignments

C# provides special compound assignment operators that simplify the coding of certain assignment statements. Let’s begin with an example. The assignment statement shown here:

x = x + 10;

can be written using a compound assignment as

x += 10;

The operator pair += tells the compiler to assign to x the value of x plus 10.

Here is another example. The statement

x = x - 100;

is the same as

x -= 100;

Both statements assign to x the value of x minus 100.

There are compound assignment operators for many of the binary operators (that is, those that require two operands). The general form of the shorthand is

var-name op = expression;

Thus, the arithmetic and logical assignment operators are

Image

Because the compound assignment statements are shorter than their noncompound equivalents, the compound assignment operators are also sometimes called the shorthand assignment operators.

The compound assignment operators provide two benefits. First, they are more compact than their “longhand” equivalents. Second, they can result in more efficient executable code (because the left-hand operand is evaluated only once). For these reasons, you will often see the compound assignment operators used in professionally written C# programs.

The Bitwise Operators

C# provides a set of bitwise operators that expand the types of problems to which C# can be applied. The bitwise operators act directly upon the bits of their operands. They are defined only for integer operands. They cannot be used on bool, float, or double.

They are called the bitwise operators because they are used to test, set, or shift the bits that comprise an integer value. Among other uses, bitwise operations are important to a wide variety of systems-level programming tasks, such as analyzing status information from a device. Table 4-1 lists the bitwise operators.

The Bitwise AND, OR, XOR, and NOT Operators

The bitwise operators AND, OR, XOR, and NOT are &, |, ^, and ~. They perform the same operations as their Boolean logic equivalents described earlier. The difference is that the bitwise operators work on a bit-by-bit basis. The following table shows the outcome of each operation using 1s and 0s:

Image

In terms of its most common usage, you can think of the bitwise AND as a way to turn bits off. That is, any bit that is 0 in either operand will cause the corresponding bit in the outcome to be set to 0. For example

Image

Image

TABLE 4-1 The Bitwise Operators

The following program demonstrates the & by using it to convert odd numbers into even numbers. It does this by turning off bit zero. For example, the low-order byte of the number 9 in binary is 0000 1001. When bit zero is turned off, this number becomes 8, or 0000 1000 in binary.

// Use bitwise AND to make a number even.

using System;

class MakeEven {
  static void Main() {
    ushort num;
    ushort i;

    for(i = 1; i <= 10; i++) {
      num = i;

      Console.WriteLine("num: " + num);

      num = (ushort) (num & 0xFFFE);

      Console.WriteLine("num after turning off bit zero: "
                         +  num + "\n");
    }
  }
}

The output from this program is shown here:

num: 1
num after turning off bit zero: 0

num: 2
num after turning off bit zero: 2

num: 3
num after turning off bit zero: 2

num: 4
num after turning off bit zero: 4

num: 5
num after turning off bit zero: 4

num: 6
num after turning off bit zero: 6

num: 7
num after turning off bit zero: 6

num: 8
num after turning off bit zero: 8

num: 9
num after turning off bit zero: 8

num: 10
num after turning off bit zero: 10

The value 0xFFFE used in the AND statement is the hexadecimal representation of 1111 1111 1111 1110. Therefore, the AND operation leaves all bits in num unchanged except for bit zero, which is set to zero. Thus, even numbers are unchanged, but odd numbers are made even by reducing their value by 1.

The AND operator is also useful when you want to determine whether a bit is on or off. For example, this program determines if a number is odd:

//  Use bitwise AND to determine if a number is odd.

using System;

class IsOdd {
  static void Main() {
    ushort num;

    num = 10;

    if((num & 1) == 1)
      Console.WriteLine("This won't display.");

    num = 11;

    if((num & 1) == 1)
      Console.WriteLine(num + " is odd.");
  }
}

The output is shown here:

11 is odd.

In the if statements, the value of num is ANDed with 1. If bit zero in num is set, the result of num & 1 is 1; otherwise, the result is zero. Therefore, the if statement can succeed only when the number is odd.

You can use the bit-testing capability of the bitwise & to create a program that uses the bitwise & to show the bits of a byte value in binary format. Here is one approach:

// Display the bits within a byte.

using System;

class ShowBits {
  static void Main() {
    int t;
    byte val;

    val = 123;
    for(t=128; t > 0; t = t/2) {
      if((val & t) != 0) Console.Write("1 ");
      if((val & t) == 0) Console.Write("0 ");
    }
  }
}

The output is shown here:

0 1 1 1 1 0 1 1

The for loop successively tests each bit in val, using the bitwise AND, to determine if it is on or off. If the bit is on, the digit 1 is displayed; otherwise, 0 is displayed.

The bitwise OR can be used to turn bits on. Any bit that is set to 1 in either operand will cause the corresponding bit in the variable to be set to 1. For example

Image

You can make use of the OR to change the make-even program shown earlier into a make-odd program, as shown here:

//  Use bitwise OR to make a number odd.

using System;

class MakeOdd {
  static void Main() {
    ushort num;
    ushort i;

    for(i = 1; i <= 10; i++) {
      num = i;

       Console.WriteLine("num: " + num);

       num = (ushort) (num | 1);

       Console.WriteLine("num after turning on bit zero: "
                         +  num + "\n");
    }
  }
}

The output from this program is shown here:

num: 1
num after turning on bit zero: 1

num: 2
num after turning on bit zero: 3

num: 3
num after turning on bit zero: 3

num: 4
num after turning on bit zero: 5

num: 5
num after turning on bit zero: 5

num: 6
num after turning on bit zero: 7

num: 7
num after turning on bit zero: 7

num: 8
num after turning on bit zero: 9

num: 9
num after turning on bit zero: 9

num: 10
num after turning on bit zero: 11

The program works by ORing each number with the value 1, because 1 is the value that produces a value in binary in which only bit zero is set. When this value is ORed with any other value, it produces a result in which the low-order bit is set and all other bits remain unchanged. Thus, a value that is even will be increased by 1, becoming odd.

An exclusive OR, usually abbreviated XOR, will set a bit on if, and only if, the bits being compared are different, as illustrated here:

Image

The XOR operator has an interesting property that is useful in a variety of situations. When some value X is XORed with another value Y, and then that result is XORed with Y again, X is produced. That is, given the sequence

R1 = X ^ Y;

R2 = R1 ^ Y;

R2 is the same value as X. Thus, the outcome of a sequence of two XORs using the same value produces the original value. This feature of the XOR can be put into action to create a simple cipher in which some integer is the key that is used to both encode and decode a message by XORing the characters in that message. To encode, the XOR operation is applied the first time, yielding the ciphertext. To decode, the XOR is applied a second time, yielding the plaintext. Of course, such a cipher has no practical value, being trivially easy to break.

// Demonstrate the XOR.

using System;
class Encode {
  static void Main() {
    char ch1 = 'H';
    char ch2 = 'i';
    char ch3 = '!';
    int key = 88;

    Console.WriteLine("Original message: " + ch1 + ch2 + ch3);

    // Encode the message.
    ch1 = (char) (ch1 ^ key);
    ch2 = (char) (ch2 ^ key);
    ch3 = (char) (ch3 ^ key);

    Console.WriteLine("Encoded message: " + ch1 + ch2 + ch3);

    // Decode the message.
    ch1 = (char) (ch1 ^ key);
    ch2 = (char) (ch2 ^ key);
    ch3 = (char) (ch3 ^ key);

    Console.WriteLine("Decoded message: " + ch1 + ch2 + ch3);
  }
}

Here is the output:

Original message: Hi!
Encoded message: q1y
Decoded message: Hi!

As you can see, the result of two XORs using the same key produces the decoded message. (Remember, this simple XOR cipher is not suitable for any real-world, practical use because it is inherently insecure.)

The unary one’s complement (NOT) operator reverses the state of all the bits of the operand. For example, if some integer called A has the bit pattern 1001 0110, then ∼A produces a result with the bit pattern 0110 1001.

The following program demonstrates the NOT operator by displaying a number and its complement in binary:

// Demonstrate the bitwise NOT.

using System;

class NotDemo {
  static void Main() {
    sbyte b = -34;

    for(int t=128; t > 0; t = t/2) {
       if((b & t) != 0) Console.Write("1 ");
       if((b & t) == 0) Console.Write("0 ");
    }
    Console.WriteLine();
    // reverse all bits
    b = (sbyte) ~b;

    for(int t=128; t > 0; t = t/2) {
      if((b & t) != 0) Console.Write("1 ");
      if((b & t) == 0) Console.Write("0 ");
    }
  }
}

Here is the output:

1 1 0 1 1 1 1 0
0 0 1 0 0 0 0 1

The Shift Operators

In C# it is possible to shift the bits that comprise an integer value to the left or to the right by a specified amount. C# defines the two bit-shift operators shown here:

Image

The general forms for these operators are shown here:

value << num-bits
value >> num-bits

Here, value is the value being shifted by the number of bit positions specified by num-bits.

A left shift causes all bits within the specified value to be shifted left one position and a zero bit to be brought in on the right. A right shift causes all bits to be shifted right one position. In the case of a right shift on an unsigned value, a 0 is brought in on the left. In the case of a right shift on a signed value, the sign bit is preserved. Recall that negative numbers are represented by setting the high-order bit of an integer value to 1. Thus, if the value being shifted is negative, each right shift brings in a 1 on the left. If the value is positive, each right shift brings in a 0 on the left.

For both left and right shifts, the bits shifted out are lost. Thus, a shift is not a rotate and there is no way to retrieve a bit that has been shifted out.

Here is a program that graphically illustrates the effect of a left and right shift. Here, an integer is given an initial value of 1, which means that its low-order bit is set. Then, eight shifts are performed on the integer. After each shift, the lower eight bits of the value are shown. The process is then repeated, except that a 1 is put in the eighth bit position, and right shifts are performed.

// Demonstrate the shift << and >> operators.

using System;

class ShiftDemo {
  static void Main() {
    int val = 1;
    for(int i = 0; i < 8; i++) {
      for(int t=128; t > 0; t = t/2) {
        if((val & t) != 0) Console.Write("1 ");
        if((val & t) == 0) Console.Write("0 ");
      }
      Console.WriteLine();
      val = val << 1; // left shift
    }
    Console.WriteLine();

    val = 128;
     for(int i = 0; i < 8; i++) {
       for(int t=128; t > 0; t = t/2) {
         if((val & t) != 0) Console.Write("1 ");
         if((val & t) == 0) Console.Write("0 ");
       }
       Console.WriteLine();
       val = val >> 1; // right shift
     }
  }
}

The output from the program is shown here:

0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 0
0 0 0 0 0 1 0 0
0 0 0 0 1 0 0 0
0 0 0 1 0 0 0 0
0 0 1 0 0 0 0 0
0 1 0 0 0 0 0 0
1 0 0 0 0 0 0 0

1 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0
0 0 0 1 0 0 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 1 0 0
0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 1

Since binary is based on powers of 2, the shift operators can be used as a way to multiply or divide an integer by 2. A shift left doubles a value. A shift right halves it. Of course, this works only as long as you are not shifting bits off one end or the other. Here is an example:

// Use the shift operators to multiply and divide by 2.

using System;

class MultDiv {
  static void Main() {
    int n;

    n = 10;
    Console.WriteLine("Value of n: " + n);

    // Multiply by 2.
    n = n << 1;
    Console.WriteLine("Value of n after n = n * 2: " + n);

    // Multiply by 4.
    n = n << 2;
    Console.WriteLine("Value of n after n = n * 4: " + n);

    // Divide by 2.
    n = n >> 1;
    Console.WriteLine("Value of n after n = n / 2: " + n);

    // Divide by 4.
    n = n >> 2;
    Console.WriteLine("Value of n after n = n / 4: " + n);
    Console.WriteLine();

    // Reset n.
    n = 10;
    Console.WriteLine("Value of n: " + n);

    // Multiply by 2, 30 times.
    n = n << 30; // data is lost
    Console.WriteLine("Value of n after left-shifting 30 places: " + n);
  }
}

The output is shown here:

Value of n: 10
Value of n after n = n * 2: 20
Value of n after n = n * 4: 80
Value of n after n = n / 2: 40
Value of n after n = n / 4: 10

Value of n: 10
Value of n after left-shifting 30 places: -2147483648

Notice the last line in the output. When the value 10 is left-shifted 30 times, information is lost because bits are shifted out of the range of an int. In this case, the garbage value produced is negative because a 1 bit is shifted into the high-order bit, which is used as a sign bit, causing the number to be interpreted as negative. This illustrates why you must be careful when using the shift operators to multiply or divide a value by 2. (See Chapter 3 for an explanation of signed vs. unsigned data types.)

Bitwise Compound Assignments

All of the binary bitwise operators can be used in compound assignments. For example, the following two statements both assign to x the outcome of an XOR of x with the value 127:

x = x ^ 127;
x ^= 127;

The ? Operator

One of C#’s most fascinating operators is the ?, which is C#’s conditional operator. The ? operator is often used to replace certain types of if-then-else constructions. The ? is called a ternary operator because it requires three operands. It takes the general form

Exp1 ? Exp2: Exp3;

where Exp1 is a bool expression, and Exp2 and Exp3 are expressions. The type of Exp2 and Exp3 must be the same (or, an implicit conversion between them must exist). Notice the use and placement of the colon.

The value of a ? expression is determined like this: Exp1 is evaluated. If it is true, then Exp2 is evaluated and becomes the value of the entire ? expression. If Exp1 is false, then Exp3 is evaluated, and its value becomes the value of the expression. Consider this example, which assigns absval the absolute value of val:

absval = val < 0 ? -val : val; // get absolute value of val

Here, absval will be assigned the value of val if val is zero or greater. If val is negative, then absval will be assigned the negative of that value (which yields a positive value).

Here is another example of the ? operator. This program divides two numbers, but will not allow a division by zero.

// Prevent a division by zero using the ?.

using System;

class NoZeroDiv {
  static void Main() {
    int result;

    for(int i = -5; i < 6; i++) {
      result = i != 0 ? 100 / i : 0;
      if(i != 0)
        Console.WriteLine("100 / " + i + " is " + result);
    }
  }
}

The output from the program is shown here:

100 / -5 is -20
100 / -4 is -25
100 / -3 is -33
100 / -2 is -50
100 / -1 is -100
100 / 1 is 100
100 / 2 is 50
100 / 3 is 33
100 / 4 is 25
100 / 5 is 20

Pay special attention to this line from the program:

result = i != 0 ? 100 / i : 0;

Here, result is assigned the outcome of the division of 100 by i. However, this division takes place only if i is not 0. When i is 0, a placeholder value of 0 is assigned to result.

You don’t actually have to assign the value produced by the ? to some variable. For example, you could use the value as an argument in a call to a method. Or, if the expressions are all of type bool, the ? can be used as the conditional expression in a loop or if statement. For example, the following program displays the results of dividing 100 by only even, nonzero values:

// Divide by only even, non-zero values.

using System;

class NoZeroDiv2 {
  static void Main() {

    for(int i = -5; i < 6; i++)
      if(i != 0 ? (i%2 == 0) : false)
        Console.WriteLine("100 / " + i + " is " + 100 / i);
  }
}

Notice the if statement. If i is zero, then the outcome of the if is false. Otherwise, if i is nonzero, then the outcome of the if is true if i is even and false if i is odd. Thus, only even, nonzero divisors are allowed. Although this example is somewhat contrived for the sake of illustration, such constructs are occasionally very useful.

Spacing and Parentheses

An expression in C# can have tabs and spaces in it to make it more readable. For example, the following two expressions are the same, but the second is easier to read:

x=10/y*(127+x);

x = 10 / y * (127 + x);

Parentheses can be used to group subexpressions, thereby effectively increasing the precedence of the operations contained within them, just like in algebra. Use of redundant or additional parentheses will not cause errors or slow down execution of the expression. You are encouraged to use parentheses to make clear the exact order of evaluation, both for yourself and for others who may have to figure out your program later. For example, which of the following two expressions is easier to read?

x = y/3-34*temp+127;

x = (y/3) - (34*temp) + 127;

Operator Precedence

Table 4-2 shows the order of precedence for all C# operators, from highest to lowest. This table includes several operators that will be discussed later in this book.

Image

TABLE 4-2 The Precedence of the C# Operators