Chapter 5
Operators

What’s in This Chapter

  • Arithmetic, concatenation, comparison, logical, and bitwise operators
  • Operator precedence
  • DateTime and TimeSpan operators
  • Operator overloading
  • Type conversion operators

Wrox.com Downloads for This Chapter

Please note that all the code examples for this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com/go/csharp5programmersref on the Download Code tab.

An operator is a basic code element that performs some operation on one or more values to create a result. The values the operator acts upon are operands. For example, in the following statement, the operator is + (addition), the operands are B and C, and the result is assigned to the variable A.

A = B + C

C# operators fall into five categories: arithmetic, concatenation, comparison, logical, and bitwise. This chapter first explains these categories and the operators they contain; it then discusses other operator issues such as precedence, assignment operators, and operator overloading.

Arithmetic Operators

The following table lists the arithmetic operators provided by C#. Most of these should be familiar to you.

OperatorPurposeExampleResult
++Incrementx++ or ++xSets x = x + 1
--Decrementx-- or --xSets x = x - 1
Negation-xSets x = -x
+Unary plus+xSets x = +x (leaves x unchanged)
*Multiplication2 * 36
/Division3.0 / 21.5
%Modulus17 mod 52
+Addition2 + 35
-Subtraction3 - 21
<<Bit left shift10110111 << 101101110
>>Bit right shift10110111 >> 101011011

The % operator returns the remainder after dividing its first operand by its second. For example, 17 % 5 = 2 because 17 = 3 5 with a remainder of 2.

Result Data Type

An operator’s result data type depends on the operands’ data types. If an expression combines values with different data types, those with narrower types (smaller ranges) are automatically promoted to the type with the wider type (greater range). For example, consider the following expression.

var x = 1.2 + 3;

The operands 1.2 and 3 are a double and an int, respectively. Because the result may not fit in an int, the value 3 is promoted to a double. The calculation is then performed and the final result is a double.

The / operator performs both integer and floating point division. If the operands are both integers, the / operator performs integer division and discards any remainder. If either of the operands has a floating point type, the / operator performs floating point division. The following code shows examples.

float x = 9 / 10;   // Result 0.0.
float y = 9f / 10;  // Result 0.9.
float z = 9 / 10f;  // Result 0.9.

The + symbol represents both numeric addition and string concatenation. The following code shows an example of string concatenation.

string firstname = "Rod";
string lastname = "Stephens";
string name = firstname + ' ' + lastname;   // Result "Rod Stephens"

The third line of code in this example uses the + operator to combine the string firstname, a space character, and the string lastname.

If one of the + operator’s operands is a string, then the result is a string and the other operand is converted into a string if necessary. For example, the following code concatenates a string, double, string, and bool.

string message = "Data point " + 37.2 + " is " + false;

The result in this example is the string “Data point 37.2 is False.”

Shift Operators

The << operator shifts the bits of an integer value to the left, padding the empty bits on the right with zeros. For example, the byte value with bits 10110111 shifted 1 bit to the left gives 01101110. The leftmost bit doesn’t fit in the byte so it is dropped.

The >> operator shifts the bits of a value to the right, padding the empty bits on the left with zeros. For example, the byte value with bits 10110111 shifted 1 bit to the right gives 01011011.

Unfortunately, you can’t put binary literals such as 10110111 in your code. Instead, you must write this value as 0xB7 in hexadecimal or 183 in decimal.

Increment and Decrement Operators

The statements ++x and x++ both increment the value of the variable x but they have slightly different side effects. These two forms of the increment operator are sometimes called pre-increment and post-increment operations, respectively. (This discussion also applies to the pre-decrement and post-decrement operators --x and x--.)

The pre-increment operator ++x increments x and then returns its new value. The post-increment operator x++ returns x’s original value and then increments x. The difference is small and in simple calculations isn’t important. For example, many programs use one or the other of these operators to increment a variable and nothing more. The following code simply increments the variable numProcessed.

numProcessed++;

These operators are also often used in for loops as in the following code. The post-increment operator is highlighted in bold.

for (int i = 0; i < 5; i++) Console.WriteLine("i: " + i);

In both these examples, the increment operation takes place on its own, so it doesn’t matter whether you use the pre-increment or post-increment version. In more complicated calculations, however, the difference can be important.

For example, suppose a bill-of-sale application has variables priceEach and numItems. The user clicks a button to add a new item to the sale, and the program uses the following code to update the total cost.

decimal totalCost = numItems++ * priceEach;

Because this code uses the post-increment operator, the calculation uses the current value numItems and then increments it afterward. This gives an incorrect result because the calculation uses the old value for numItems. For example, suppose priceEach is 10 and numItems is 1 when this statement executes. The code calculates totalCost = 10 * 1 = 10 and then increments numItems to 2.

The following version uses the pre-increment operator.

decimal totalCost = ++numItems * priceEach;

This version increments numItems and then performs the calculation so it gets the correct result. If priceEach is 10 and numItems is 1 when this statement executes, the code first increments numItems to 2 and then calculates totalCost = 10 * 2 = 20, which is the correct result.

Comparison Operators

Comparison operators compare one value to another and return a boolean value (true or false), depending on the result. The following table lists the comparison operators provided by C#. The first six (==, !=, <, <=, >, and >=) are relatively straightforward. Note that the logical negation operator ! is not a comparison operator, so it is not listed here. It is described in the next section, “Logical Operators.”

OperatorPurposeExampleResult
==EqualsA == Btrue if A equals B
!=Not equalsA != Btrue if A does not equal B
<Less thanA < Btrue if A is less than B
<=Less than or equal toA <= Btrue if A is less than or equal to B
>Greater thanA > Btrue if A is greater than B
>=Greater than or equal toA >= Btrue if A is greater than or equal to B
isObject is or inherits from a certain typeobj is Managertrue if obj is an object that inherits from Manager

When its operands are reference types, the == and != operators compare the operands, not the values to which they refer. For example, the following code defines two Person objects and sets them equal to the same value.

Person person1 = new Person();
Person person2 = person1;

After this code executes, person1 and person2 refer to the same Person object so the == operator reports them as equal.

The following code creates two different Person objects.

Person person1 = new Person() { FirstName = "Zaphod", LastName = "Beeblebrox" };
Person person2 = new Person() { FirstName = "Zaphod", LastName = "Beeblebrox" };

These are two separate objects even though they happen to have the same FirstName and LastName values. Even if all the objects’ properties and fields have the same values, these are still two separate objects so the == operator reports them as different.

Logical Operators

Logical operators combine two boolean values and return true or false, depending on the result. The following table summarizes C#’s logical operators.

OperatorPurposeExampleResult
!Negation!Atrue if A is false
&AndA & Btrue if A and B are both true
|OrA | Btrue if A or B or both are true
^Xor (Exclusive Or)A ^ Btrue if A is true or B is true but both are not true
&&And with short-circuit evaluationA && Btrue if A and B are both true (see the following notes)
||Or with short-circuit evaluationA || Btrue if A or B or both are true (see notes)

The operators !, &, and | are relatively straightforward.

“Xor” stands for “exclusive or,” and the Xor operator returns true if one but not both of its operands is true. The expression A ^ B is true if A is true or B is true but both are not true.

Xor is useful for situations in which exactly one of two things should be true. For example, suppose you run a small software conference with two tracks, so two talks are going on at any given time. Each attendee should sign up for one talk in each time slot but cannot sign up for both because they’re at the same time. You might use code similar to the following to check whether an attendee has signed up for either talk 1a or talk 1b but not both:

if (talk1a ^ talk1b)
{
    // This is okay
    ...
}

The && and || operators are similar to the & and | operators, except that they provide short-circuit evaluation. In short-circuit evaluation, C# is allowed to stop evaluating operands if it can deduce the final result without them. For example, consider the expression A && B. If C# evaluates the value A and discovers that it is false, the program knows that the expression A && B is also false no matter what value B has, so it doesn’t need to evaluate B.

Similarly for the expression A || B, if A is true, then the entire expression is true no matter what value B has, so the program doesn’t need to evaluate B.

Whether the program evaluates both operands doesn’t matter much if A and B are simple boolean variables. However, assume that they are time-consuming methods. For example, the TimeConsumingFunction routine might need to look up values in a database or download data from a website. In that case, not evaluating the second operand might save a lot of time.

if (TimeConsumingFunction("A") && TimeConsumingFunction("B"))
{
    ...
}

Because && and || do the same thing as & and | but are sometimes faster, you might wonder why you would ever use & and |. The main reason is that the operands may have side effects. A side effect is some action a method performs that is not obviously part of the method. For example, suppose that the NumEmployees method opens an employee database and returns the number of employee records, leaving the database open. The fact that this method leaves the database open is a side effect.

Now, suppose that the NumCustomers function similarly opens the customer database, and then consider the following statement.

if ((NumEmployees() > 0) && (NumCustomers() > 0))
{
    ...
}

After this code executes, you cannot be certain which databases are open. If NumEmployees returns 0, the && operator’s first operand is false, so it doesn’t evaluate the NumCustomers method, and that method doesn’t open the customer database.

The && and || operators can improve application performance under some circumstances. However, to avoid possible confusion and long debugging sessions, do not use && or || with operands that have side effects.

Bitwise Operators

Bitwise operators work much like logical operators do, except they compare integer values one bit at a time.

Many programs use bitwise operators for a variety of purposes. Some of the most common include:

  • Reading and setting bit fields—Here the bits in a single number have specific meanings. For example, suppose you want to store a value indicating whether an object should be attached to the left, right, top, and bottom edges of its container. You could store that information with four separate boolean values, or you could store it as four bits in a single value.
  • Working with devices—Many devices such as communications ports use bits to get and set information about the device.
  • Encryption and compression—These operations often need to work with bits.
  • Graphics—Many graphics algorithms use bits to represent colors and other important values. For example, different bits in a single number may represent a pixel’s red, green, and blue color components.

The bitwise negation operator ~ flips the bits in its operand from 1 to 0 and vice versa. The following shows an example:

 ~10110111
= 01001000

The bitwise And operator & places a 1 in a result bit if both of its operands have a 1 in that position. The following shows an example:

  10101010
& 00110110
= 00100010

The bitwise Or operator | places a 1 in the result if either of its operands has a 1 in the corresponding position. The following shows an example:

  10101010
| 00110110
= 10111110

The bitwise Xor operator ^ places a 1 bit in the result if exactly one of its operands, but not both, has a 1 in the corresponding position. The following shows an example:

  10101010
^ 00110110
= 10011100

There are no bitwise equivalents for the && and || operators.

Conditional and Null-coalescing Operators

The conditional operator ?:, which is sometimes called the ternary operator, takes three operands. The first operand is a boolean value. If that value is true, the operator returns its second operand. If the first operand is false, the operator returns its third operand.

For example, suppose the variable amount holds a dollar value and you want to display it in the Label control named amountLabel. If amount is negative, you want to display its value in red. If amount is not negative, you want to display its value in blue. The following code sets the amountLabel control’s text and color appropriately.

amountLabel.Text = amount.ToString("c");
amountLabel.ForeColor = (amount < 0) ? Color.Red : Color.Blue;

One way you might use the conditional operator is to set one variable equal to another variable’s value or some default value if the second variable’s value is null.

For example, suppose a sales program is trying to place an order. If the order is for an existing customer, the variable customer is a Customer object representing the customer. If this is a new customer, then customer is null. The following code uses the conditional operator to set variable orderedBy to either the existing customer or a new Customer object.

Customer orderedBy = (customer != null) ? customer : new Customer();

This code essentially sets orderedBy equal to customer or a default value (a new Customer object) if customer is null. This is a common enough procedure that C# provides a special operator to do just this.

The null-coalescing operator ?? takes two operands. It returns its left operand if its value is not null. If the left operand is null, it returns its right operand. The ?? operator’s left operand can be any value that might be null including a reference variable or a nullable type such as int?.

The following code shows the preceding example rewritten to use the ?? instead of ?:.

Customer orderedBy = customer ?? new Customer();

Assignment Operators

A fairly common operation in C# programs is to set a variable equal to its value after performing some operation on it. For example, the following code adds 10 to the value x.

x = x + 10;

To make this sort of operation easier, C# provides a set of assignment operators. These consist of a normal operator followed by =. For example, the following statement shows the preceding code rewritten to use the += assignment operator.

x += 10;

This operator basically means, “Set x equal to its current value plus 10.”

The complete list of assignment operators is: =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, and >>=.

Operator Precedence

When C# evaluates a complex expression, it must decide the order in which to evaluate the operators. For example, consider the expression 1 + 2 * 3 / 4 + 2. The following text shows three orders in which you might evaluate this expression to get three different results.

1 + (2 * 3) / (4 + 2) = 1 + 6 / 6 = 2
1 + ((2 * 3) / 4) + 2 = 4
((1 + 2) * 3) / (4 + 2) = 1

Precedence determines which operator C# executes first. For example, the C# precedence rules say the program should evaluate multiplication and division before addition, so the second equation above is the correct interpretation.

The following table lists the operators in order of precedence. When evaluating an expression, the program evaluates an operator before it evaluates those lower than it in the list.

OperatorDescription
( )Grouping (parentheses)
x++
x--
Post-increment
Post-decrement
+
-
!
~
++x
--x
(T)
Unary plus
Numeric negation
Logical negation
Bitwise negation
Pre-increment
Pre-decrement
Casting
*
/
%
Multiplication
Division
Modulus
+
+
-
Concatenation
Addition
Subtraction
<<
>>
Left shift
Right shift
<
>
<=
>=
is
Less than
Greater than
Less than or equal to
Greater than or equal to
Inherits from
==
!=
Equals
Does not equal
&Logical And
^Logical Xor
|Logical Or
&&Conditional And
||Conditional Or
??Null-coalescing
?:Conditional
=
+=
-=
...
Assignment operators

When operators are in the same section in the table, or if an expression contains more than one instance of the same operator, the program evaluates them in left-to-right order.

For example, % and * are in the same section in the table, so the expression 17 % 5 * 5 is evaluated as (17 % 5) * 5 = 10 not as 17 % (5 * 5) = 17.

Parentheses are not actually operators, but they do have a higher precedence than the true operators, so they’re listed to make the table complete. You can always use parentheses to explicitly dictate the order in which C# performs an evaluation.

The StringBuilder Class

The + operator is useful for concatenating a few strings together, but if you must combine a large number of strings, you may get better performance by using the StringBuilder class. This class is optimized for performing long sequences of concatenations to build big strings.

The following code compares the performance of string concatenation and the StringBuilder class.

const int numTrials = 10000;
System.Diagnostics.Stopwatch watch =
    new System.Diagnostics.Stopwatch();

// Test +=.
watch.Start();
for (int i = 0; i < numTrials; i++)
{
    string test = "";
    for (int j = 0; j < 1000; j++) test += "A";
}
watch.Stop();
Console.WriteLine("Using += took " +
    watch.Elapsed.TotalSeconds.ToString("0.00") + " seconds");

// Test StringBuilder.
watch.Reset();
watch.Start();
for (int i = 0; i < numTrials; i++)
{
    StringBuilder builder = new StringBuilder();
    for (int j = 0; j < 1000; j++) builder.Append("A");
    string test = builder.ToString();
}
watch.Stop();
Console.WriteLine("Using StringBuilder took " +
    watch.Elapsed.TotalSeconds.ToString("0.00") + " seconds");

This code performs 10,000 trials using string concatenation and the StringBuilder class. For each trial, it builds a string containing the character A 1,000 times.

To test string concatenation, the program enters a loop that uses the += operator 1,000 times. To test the StringBuilder class, the program creates a StringBuilder object and calls its Append method 1,000 times.

After each test, the program displays the elapsed time in the Console window. The following text shows a sample output.

Using += took 6.10 seconds
Using StringBuilder took 0.19 seconds

For small pieces of code, the difference between concatenation and using StringBuilder is negligible. If you need to concatenate a dozen or so strings once, using a StringBuilder won’t make much difference in run time, will make the code more confusing, and may even slow performance slightly.

However, if you make huge strings built up in pieces, or if you build simpler strings but many times in a loop, StringBuilder may make your program run faster.

DateTime and TimeSpan Operations

The DateTime data type is fundamentally different from other data types. When you perform an operation on most data types, you get a result that has the same type or that is at least of some compatible type. For example, if you subtract two ints, the result is an int.

In contrast, if you subtract two DateTime variables, the result is not a DateTime. For example, what’s August 7 minus July 20? It doesn’t make sense to think of the result as a date.

Instead, C# defines the difference between two DateTimes as a TimeSpan, a data type that represents an elapsed time. In this example, August 7 minus July 20 is 18 days. (And yes, TimeSpans know all about leap years.)

The following equations define DateTime and TimeSpan arithmetic.

DateTime – DateTime = TimeSpan
DateTime + TimeSpan = DateTime
TimeSpan + TimeSpan = TimeSpan
TimeSpan – TimeSpan = TimeSpan

The TimeSpan class also defines unary negation (timespan2 = -timespan1), but other operations (such as multiplying a TimeSpan by a number) are not defined. In some cases you can still perform the calculation if you must.

For example, the following code makes variable timespan2 twice as long as timespan1.

TimeSpan timespan2 = new TimeSpan(timespan1.Ticks * 2);

This code takes the number of ticks in timespan1, multiplies that value by 2, and passes the result into a TimeSpan class constructor to make the new TimeSpan value. (A tick is 100 nanoseconds or 100 billionths of a second.)

Sometimes using operators to combine DateTime and TimeSpan values can be a bit cumbersome. For example, the following statement adds 7 days to the current date. (As you can probably guess, DateTime.Now returns a DateTime that represents the current date and time.)

DateTime nextWeek = DateTime.Now + new TimeSpan(7, 0, 0, 0, 0);

To make these sorts of calculations easier to read, DateTime provides methods for performing common operations.

The Add method returns a new DateTime representing a DateTime’s value plus the duration represented by a TimeSpan. (DateTime also provides a Subtract method that subtracts a TimeSpan.) For example, the following code returns the current date and time plus 7 days.

DateTime.Now.Add(new TimeSpan(7, 0, 0, 0, 0));

This isn’t a great improvement in readability over the previous version, but the other DateTime methods are easier to read. Each adds a given number of a specific time unit to a DateTime. For example, the following code uses the AddDays method to add 7 days to the current date and time.

DateTime.Now.AddDays(7);

This code does the same thing as the previous two examples but is easier to read.

The DateTime methods that add durations are AddYears, AddMonths, AddDays, AddHours, AddMinutes, AddSeconds, AddMilliseconds, and AddTicks.

DateTime does not provide methods for subtracting specific kinds of durations such as SubtractDays. Fortunately, you can pass a negative value into the other methods. For example, the following statement returns the date and time 7 days before the current date and time.

DateTime.Now.AddDays(-7);

Operator Overloading

C# defines operators for expressions that use standard data types such as int and bool. It defines a few operators such as is for object references, but operators such as * and % don’t make sense for objects in general.

However, you can define those operators for your structures and classes by using the operator statement. This is a more advanced topic, so if you’re relatively new to C#, you may want to skip this section and come back to it later, perhaps after you have read Chapter 12, “Classes and Structures.”

To overload an operator in a class, create a static method that returns the appropriate data type. Instead of giving the method a name, use the keyword operator followed by the operator symbol you want to overload. Next, define the parameters that the operator takes. Finally, write the code that the operator should return.

For example, suppose you want to define a Complex class to represent complex numbers. The following code shows the beginning of the class. Here Re holds the number’s real part and Im holds the number’s imaginary part. (If you don’t remember how complex numbers work, see http://en.wikipedia.org/wiki/Complex_number.)

public class Complex
{
    public double Re = 0, Im = 0;
}

You could define methods such as Add and Multiply to add and multiply Complex objects, but the + and * operators would be much more intuitive and easy to read.

The following code overrides the + operator for this class.

public static Complex operator +(Complex operand1, Complex operand2)
{
    return new Complex()
    {
        Re = operand1.Re + operand2.Re,
        Im = operand1.Im + operand2.Im
    };
}

The operator’s declaration indicates that the + operator takes two Complexes as parameters and returns a Complex. The code creates a new Complex object and uses object initialization syntax to set its Re and Im fields equal to the sums of the operands’ Re and Im values. It then returns the new object.

The following code shows a * operator for this class.

public static Complex operator *(Complex operand1, Complex operand2)
{
    return new Complex()
        {
            Re = operand1.Re * operand2.Re - operand1.Im * operand2.Im,
            Im = operand1.Re * operand2.Im + operand1.Im * operand2.Re
        };
}

Some operators such as unary negation (-x) and unary plus (+x) take only one operand. To overload an operator that has only one operand, give the operand only one parameter.

The following code shows a unary negation operator for the Complex class.

public static Complex operator -(Complex operand1)
{
    return new Complex()
        {
            Re = -operand1.Re,
            Im = -operand1.Im
        };
}

Unary operators that you can overload include: +, -, !, ~, ++, --, true, and false. Binary operators that you can overload include: +, -, *, /, %, &, |, ^, <<, and >>. Note that the second operand for the shift operators << and >> must be an int.

The assignment operators are automatically overloaded if you overload the corresponding operator. For example, if you overload *, then C# overloads *= for you.

The following sections provide some extra detail about different kinds of overloaded operators.

Comparison Operators

Comparison operators that you can overload include ==, !=, <, >, <=, and >=. These operators must be overloaded in pairs. For example, if you overload <, you must also overload >. The pairs are < and >, and <= and >=, and == and !=. Similarly the true and false operators come as a pair.

There are two notions of equality for objects. Reference equality means two references point to the same object. Value equality means two objects contain the same values.

For example, if two Person objects refer to the same object, they have reference equality. If they refer to different objects that happen to have the same properties and fields, they have value equality.

Normally the == and != operators test reference equality, so if that’s the kind of test you want, you don’t need to overload these operators.

For the Complex class, two instances of the class that represent the same number should be treated as equal, so the class should implement value equality and therefore the == and != operators.

If you overload those operators, Visual Studio also expects you to override the Equals and GetHashCode methods (which all classes inherit from the ultimate base class, Object).

To summarize, if you want a class to provide value equality, you must overload == and !=, and you must override Equals and GetHashCode. This is some work but it’s not quite as bad as it sounds.

The following code shows the overridden Equals method.

public override bool Equals(object obj)
{
    if (obj == null) return false;
    if (!(obj is Complex)) return false;

    Complex complex = obj as Complex;
    return ((this.Re == complex.Re) && (this.Im == complex.Im));
}

This is an instance method so there is some instance of the Complex class for which the code is running. The this keyword provides a reference to that object. (Again if this is too confusing, come back to it after you read Chapter 12.)

The method starts by checking the input parameter. If that parameter is null, it cannot equal the object referred to by this so the method returns false. (The object this cannot be null. Otherwise for what object is the code running?)

Next if the parameter is not a Complex, for example, if it’s a Person or Frog, the method returns null.

If the method has not already returned by this point, you know the parameter is a Complex and is not null. In that case the method returns true if the real and imaginary parts of the parameter are the same as those for the this object.

The following code shows the GetHashCode method.

public override int GetHashCode()
{
    return (Re.GetHashCode() + Im).GetHashCode();
}

This method must return a hash code for use by algorithms and data structures such as the one provided by the HashTable class. This method invokes the real part’s GetHashCode method. It adds the imaginary part to the result and invokes the sum’s GetHashCode method.

The following code shows the == operator.

public static bool operator ==(Complex operand1, Complex operand2)
{
    // If both refer to the same object (reference equality), return true.
    if ((object)operand1 == (object)operand2) return true;

    // If one is null but not the other, return false.
    if (((object)operand1 == null) || ((object)operand2 == null)) return false;

    // Compare the field values.
    return (operand1.Re == operand2.Re) && (operand1.Im == operand2.Im);
}

The code first compares the two operands to see if they refer to the same object. It first casts both operands to the generic object type, so the program doesn’t try to use the == operator that we are currently defining to compare them. Without these casts, this statement recursively calls this same == operator, which calls the same == operator, and so on until the recursive calls fill the stack and the program crashes.

If the operands don’t have reference equality, the code checks whether one of them is null. If they were both null, they would have reference equality, so the method would already have returned true. If that didn’t happen and either of the operands is null, one is null and the other is not, so the method returns false.

If the method has not yet returned, both operands are not null. In that case the method returns true if their real and imaginary parts match.

The following code shows the != operator.

public static bool operator !=(Complex operand1, Complex operand2)
{
    return !(operand1 == operand2);
}

This operator simply uses the == operator to compare the operands and negates the result.

Logical Operators

The true and false operators are a bit confusing. The true operator should return the boolean value true if its operand should be regarded as true. Similarly, the false operator should return the boolean value true if its operand should be regarded as false.

Note that an operand might be neither true nor false depending on what it represents. For example, an object reference might be null, in which case you might not want to consider it true or false.

If you define the true and false operators, objects of the class can control tests such as those used by the if statement and while loops.

For example, suppose you consider a Complex object to be true if either its real or imaginary part is non-zero. Then if the variable complex is a Complex, the following code would work.

if (complex) Console.WriteLine("complex is true");

You cannot overload the && and || operators, but if you overload true, false, &, and |, these are defined for you and they use short-circuit evaluation.

Type Conversion Operators

Type conversion operators enable the program to convert values from one type to another. For example, you can regard any complex number as a real number with imaginary part 0. That means you can convert a float, double, or other numeric value into a Complex.

The reverse is not always true. Not all complex numbers are also real numbers. To use terminology defined in Chapter 4, “Data Types, Variables, and Constants,” converting from a double to a Complex is a widening conversion and converting from a Complex to a double is a narrowing conversion.

To define a widening conversion, create an operator overload where the operator’s name is the new data type. Use the keyword implicit to indicate that this is a widening conversion, so the code doesn’t need to explicitly cast to make the conversion.

The following code shows an implicit conversion operator that converts a double to a Complex.

public static implicit operator Complex(double real)
{
    return new Complex() { Re = real, Im = 0 };
}

This operator returns a new Complex with the real part equal to the double parameter and imaginary part 0.

After you define this operator, you can implicitly convert a double into a Complex as in the following code.

Complex complex = 12.3;

Note that C# already knows how to implicitly convert from int, long, float, and other numeric types to double so it can convert those types into a Complex. For example, the following statement converts the character value 'A' into the double 65.0. (65 is the Unicode value for A.) It then uses the conversion operator to convert that into a Complex.

Complex complex = 'A';

To define a narrowing conversion, create an operator overload as before, but replace the implicit keyword with the keyword explicit. The following code shows an explicit conversion operator that converts from Complex to double.

public static explicit operator double(Complex complex)
{
    return complex.Re;
}

After you define this operator, you can explicitly convert a Complex into a double as in the following code.

Complex complex = new Complex() { Re = 13, Im = 37 };
double real = (double)complex;

Although you normally cannot make two versions of a method that differ only in their return types, you can do that for conversion operators. When the program tries to make a conversion, it can tell by the type of the result which conversion operator to use. For example, the following code defines a conversion from Complex to int.

public static explicit operator int(Complex complex)
{
    return (int)complex.Re;
}

In this case it may be better to just have one conversion to double and let the program convert the double to int.

It is easy to get carried away with operator overloading. Just because you can define an operator for a class doesn’t mean you should. For example, you might concoct some meaning for + with the Employee class, but it would probably be a counterintuitive operation. It would probably be better to write a method with a meaningful name instead of an ambiguous operator such as + or >>.

Summary

A program uses operators to manipulate variables, constants, and literal values to produce new results. In most cases, using operators is straightforward and intuitive.

Operator precedence determines the order in which C# applies operators when evaluating an expression. In cases in which an expression’s operator precedence is unclear, add parentheses to make the order obvious. Even if you don’t change the way that C# handles the statement, you can make the code more understandable and avoid possibly time-consuming bugs.

Because the string type is a reference type that usually acts like a value type, performing long sequences of concatenations can be inefficient. The StringBuilder class makes that kind of string processing faster. If your program works only with a few short strings, using the string data type will probably be fast enough and will make your code easier to read. However, if your application builds enormous strings or concatenates a huge number of strings, you may save a noticeable amount of time by using StringBuilder.

The DateTime data type also behaves differently from other types. Normal operators such as + and have different meanings for this class. For example, subtracting two DateTimes gives a TimeSpan as a result, not another DateTime. These operations generally make sense if you think carefully about what dates and time spans are.

Addition, subtraction, and other operations have special meaning for DateTime and TimeSpan values. Similarly, you can override operators to perform special operations on your classes. Defining / or >> may not make much sense for the Employee, Customer, or Order classes but in some cases custom operators can make your code more readable.

A program uses operators to combine variables to create new results. A typical program may perform the same set of calculations many times under different circumstances. For example, a point-of-sales program might need to add up the prices of the items in a customer order in many different parts of the program. Instead of performing that calculation every time it needed, you can move the calculation into a method and then call the method to perform the calculation. Chapter 6, “Methods,” explains how you can use methods to break a program into manageable pieces that you can then reuse to make performing the calculations simpler, more consistent, and easier to debug and maintain.

Exercises

  1. Can you use both the pre- and post-increment operators on the same variable as in ++x++? If you can’t figure it out, try it and try to understand what Visual Studio tells you about it.
  2. Sometimes the conditional and null-coalescing operators can make the code confusing, particularly if their operands are complicated expressions. Rewrite the following code to use if statements instead of ?: and ??.
    amountLabel.ForeColor = (amount < 0) ? Color.Red : Color.Blue;
    Customer orderedBy = customer ?? new Customer();
  1. In the section “Comparison Operators” the code for the overloaded == operator does not check whether both operands are null. Why does it not need to do that?
  2. Create a subtraction operator for the Complex class described in this chapter.
  3. Create a Fraction class and define the * and / operators for it.
  4. Create a conversion operator to convert Fraction to double. Is this a widening or narrowing conversion?
  5. Create a > operator for the Fraction class.
  6. Create an == operator for the Fraction class.
  7. Calculate the result of each of the following statements.
    1. 1 + 2 * 3 - 4 / 5
    2. 9 * 5 / 10
    3. 2 * 5 / 10
    4. 2 / 10 * 5
    5. 12 / 6 * 4 / 8
  8. Add parentheses to the following expressions to make them true.
    1. 4 * 4 - 4 / 4 + 4 = 19
    2. 4 * 4 - 4 / 4 + 4 = 16
    3. 4 * 4 - 4 / 4 + 4 = 11
    4. 4 * 4 - 4 / 4 + 4 = 4
    5. 4 * 4 - 4 / 4 + 4 = 0
  9. Before each of the following statements, int variable x holds the value 11. What are the values of x and y after each of the following statements?
    1. int y = x / 4;
    2. int y = x++ / 4;
    3. int y = ++x / 4;
    4. float y = x / 4;
    5. double y = x / 4f;
  10. If x is an int variable holding the value 7, then the statement float y = x / 2 sets y equal to 3.0. Give three ways to modify this statement to make y equal 3.5.
  11. If x is an int variable holding the value 7, why does the statement float y = x / 2.0 raise an error? How could you fix it?
  12. C# provides |= and &= operators but does not provide ||= or &&= operators. If those operators existed, what would they do? Would they provide any additional benefit?
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.13.76