Operator Overloading - Microsoft® Visual C#® 2012 Step by Step (2012)

Microsoft® Visual C#® 2012 Step by Step (2012)

Chapter 22. Operator Overloading

After completing this chapter, you will be able to

§ Implement binary operators for your own types.

§ Implement unary operators for your own types.

§ Write increment and decrement operators for your own types.

§ Understand the need to implement some operators as pairs.

§ Implement implicit conversion operators for your own types.

§ Implement explicit conversion operators for your own types.

You have made a great deal of use of the standard operator symbols (such as + and ) to perform standard operations (such as addition and subtraction) on types (such as int and double). Many of the built-in types come with their own predefined behaviors for each operator. You can also define how operators should behave for your own structures and classes, which is the subject of this chapter.

Understanding Operators

It is worth recapping some of the fundamental aspects of operators before delving into the details of how they work and how you can overload them. In summary:

§ You use operators to combine operands together into expressions. Each operator has its own semantics, dependent on the type it works with. For example, the + operator means “add” when used with numeric types or “concatenate” when used with strings.

§ Each operator has a precedence. For example, the * operator has a higher precedence than the + operator. This means that the expression a + b * c is the same as a + (b * c).

§ Each operator also has an associativity to define whether the operator evaluates from left to right or from right to left. For example, the = operator is right-associative (it evaluates from right to left), so a = b = c is the same as a = (b = c).

§ A unary operator is an operator that has just one operand. For example, the increment operator (++) is a unary operator.

§ A binary operator is an operator that has two operands. For example, the multiplication operator (*) is a binary operator.

Operator Constraints

You have seen throughout this book that C# enables you to overload methods when defining your own types. C# also allows you to overload many of the existing operator symbols for your own types, although the syntax is slightly different. When you do this, the operators you implement automatically fall into a well-defined framework with the following rules:

§ You cannot change the precedence and associativity of an operator. The precedence and associativity are based on the operator symbol (for example, +) and not on the type (for example, int) on which the operator symbol is being used. Hence, the expression a + b * c is always the same asa + (b * c), regardless of the types of a, b, and c.

§ You cannot change the multiplicity (the number of operands) of an operator. For example, * (the symbol for multiplication) is a binary operator. If you declare a * operator for your own type, it must be a binary operator.

§ You cannot invent new operator symbols. For example, you can’t create a new operator symbol, such as ** for raising one number to the power of another number. You’d have to create a method for that.

§ You can’t change the meaning of operators when applied to built-in types. For example, the expression 1 + 2 has a predefined meaning, and you’re not allowed to override this meaning. If you could do this, things would be too complicated!

§ There are some operator symbols that you can’t overload. For example, you can’t overload the dot (.) operator, which indicates access to a class member. Again, if you could do this, it would lead to unnecessary complexity.

TIP

You can use indexers to simulate [ ] as an operator. Similarly, you can use properties to simulate assignment (=) as an operator, and you can use delegates to mimic a function call as an operator.

Overloaded Operators

To define your own operator behavior, you must overload a selected operator. You use methodlike syntax with a return type and parameters, but the name of the method is the keyword operator together with the operator symbol you are declaring. For example, the following code shows a user-defined structure named Hour that defines a binary + operator to add together two instances of Hour:

struct Hour

{

public Hour(int initialValue)

{

this.value = initialValue;

}

public static Hour operator +(Hour lhs, Hour rhs)

{

return new Hour(lhs.value + rhs.value);

}

...

private int value;

}

Notice the following:

§ The operator is public. All operators must be public.

§ The operator is static. All operators must be static. Operators are never polymorphic and cannot use the virtual, abstract, override, or sealed modifiers.

§ A binary operator (such as the + operator shown earlier) has two explicit arguments, and a unary operator has one explicit argument. (C++ programmers should note that operators never have a hidden this parameter.)

TIP

When declaring highly stylized functionality (such as operators), it is useful to adopt a naming convention for the parameters. For example, developers often use lhs and rhs (acronyms for left-hand side and right-hand side, respectively) for binary operators.

When you use the + operator on two expressions of type Hour, the C# compiler automatically converts your code to a call to your operator + method. The C# compiler transforms the following code:

Hour Example(Hour a, Hour b)

{

return a + b;

}

to this:

Hour Example(Hour a, Hour b)

{

return Hour.operator +(a,b); // pseudocode

}

Note, however, that this syntax is pseudocode and not valid C#. You can use a binary operator only in its standard infix notation (with the symbol between the operands).

There is one final rule that you must follow when declaring an operator: at least one of the parameters must always be of the containing type. In the preceding operator + example for the Hour class, one of the parameters, a or b, must be an Hour object. In this example, both parameters areHour objects. However, there could be times when you want to define additional implementations of operator + that add, for example, an integer (a number of hours) to an Hour object—the first parameter could be Hour, and the second parameter could be the integer. This rule makes it easier for the compiler to know where to look when trying to resolve an operator invocation, and it also ensures that you can’t change the meaning of the built-in operators.

Creating Symmetric Operators

In the preceding section, you saw how to declare a binary + operator to add together two instances of type Hour. The Hour structure also has a constructor that creates an Hour from an int. This means that you can add together an Hour and an int—you just have to first use the Hourconstructor to convert the int to an Hour. For example:

Hour a = ...;

int b = ...;

Hour sum = a + new Hour(b);

This is certainly valid code, but it is not as clear or concise as adding together an Hour and an int directly, like this:

Hour a = ...;

int b = ...;

Hour sum = a + b;

To make the expression (a + b) valid, you must specify what it means to add together an Hour (a, on the left) and an int (b, on the right). In other words, you must declare a binary + operator whose first parameter is an Hour and whose second parameter is an int. The following code shows the recommended approach:

struct Hour

{

public Hour(int initialValue)

{

this.value = initialValue;

}

...

public static Hour operator +(Hour lhs, Hour rhs)

{

return new Hour(lhs.value + rhs.value);

}

public static Hour operator +(Hour lhs, int rhs)

{

return lhs + new Hour(rhs);

}

...

private int value;

}

Notice that all the second version of the operator does is construct an Hour from its int argument and then call the first version. In this way, the real logic behind the operator is held in a single place. The point is that the extra operator + simply makes existing functionality easier to use. Also, notice that you should not provide many different versions of this operator, each with a different second parameter type—instead, cater to the common and meaningful cases only, and let the user of the class take any additional steps if an unusual case is required.

This operator + declares how to add together an Hour as the left operand and an int as the right operand. It does not declare how to add together an int as the left operand and an Hour as the right operand:

int a = ...;

Hour b = ...;

Hour sum = a + b; // compile-time error

This is counterintuitive. If you can write the expression a + b, you expect to also be able to write b + a. Therefore, you should provide another overload of operator +:

struct Hour

{

public Hour(int initialValue)

{

this.value = initialValue;

}

...

public static Hour operator +(int lhs, Hour rhs)

{

return new Hour(lhs) + rhs;

}

...

private int value;

}

NOTE

C++ programmers should notice that you must provide the overload yourself. The compiler won’t write the overload for you or silently swap the sequence of the two operands to find a matching operator.

Operators and Language Interoperability

Not all languages that execute using the common language runtime (CLR) support or understand operator overloading. If you are creating classes that you want to be able to use from other languages, if you overload an operator, you should provide an alternative mechanism that supports the same functionality. For example, suppose you implement operator + for the Hour structure:

public static Hour operator +(Hour lhs, int rhs)

{

...

}

If you need to be able to use your class from a Microsoft Visual Basic application, you should also provide an Add method that achieves the same thing:

public static Hour Add(Hour lhs, int rhs)

{

...

}

Understanding Compound Assignment Evaluation

A compound assignment operator (such as +=) is always evaluated in terms of its associated simple operator (such as +). In other words, the statement

a += b;

is automatically evaluated like this:

a = a + b;

In general, the expression a @= b (where @ represents any valid operator) is always evaluated as a = a @ b. If you have overloaded the appropriate simple operator, the overloaded version is automatically called when you use its associated compound assignment operator. For example:

Hour a = ...;

int b = ...;

a += a; // same as a = a + a

a += b; // same as a = a + b

The first compound assignment expression (a += a) is valid because a is of type Hour, and the Hour type declares a binary operator + whose parameters are both Hour. Similarly, the second compound assignment expression (a += b) is also valid because a is of type Hour and b is of typeint. The Hour type also declares a binary operator + whose first parameter is an Hour and whose second parameter is an int. Note, however, that you cannot write the expression b += a because that’s the same as b = b + a. Although the addition is valid, the assignment is not, because there is no way to assign an Hour to the built-in int type.

Declaring Increment and Decrement Operators

C# allows you to declare your own version of the increment (++) and decrement (––) operators. The usual rules apply when declaring these operators: they must be public, they must be static, and they must be unary (they can take only a single parameter). Here is the increment operator for the Hour structure:

struct Hour

{

...

public static Hour operator ++(Hour arg)

{

arg.value++;

return arg;

}

...

private int value;

}

The increment and decrement operators are unique in that they can be used in prefix and postfix forms. C# cleverly uses the same single operator for both the prefix and postfix versions. The result of a postfix expression is the value of the operand before the expression takes place. In other words, the compiler effectively converts the code

Hour now = new Hour(9);

Hour postfix = now++;

to this:

Hour now = new Hour(9);

Hour postfix = now;

now = Hour.operator ++(now); // pseudocode, not valid C#

The result of a prefix expression is the return value of the operator, so the C# compiler effectively transforms the code

Hour now = new Hour(9);

Hour prefix = ++now;

to this:

Hour now = new Hour(9);

now = Hour.operator ++(now); // pseudocode, not valid C#

Hour prefix = now;

This equivalence means that the return type of the increment and decrement operators must be the same as the parameter type.

Comparing Operators in Structures and Classes

Be aware that the implementation of the increment operator in the Hour structure works only because Hour is a structure. If you change Hour into a class but leave the implementation of its increment operator unchanged, you will find that the postfix translation won’t give the correct answer. If you remember that a class is a reference type and if you revisit the compiler translations explained earlier, you can see why the operators for the Hour class no longer function as expected:

Hour now = new Hour(9);

Hour postfix = now;

now = Hour.operator ++(now); // pseudocode, not valid C#

If Hour is a class, the assignment statement postfix = now makes the variable postfix refer to the same object as now. Updating now automatically updates postfix! If Hour is a structure, the assignment statement makes a copy of now in postfix, and any changes to now leave postfix unchanged, which is what you want.

The correct implementation of the increment operator when Hour is a class is as follows:

class Hour

{

public Hour(int initialValue)

{

this.value = initialValue;

}

...

public static Hour operator ++(Hour arg)

{

return new Hour(arg.value + 1);

}

...

private int value;

}

Notice that operator ++ now creates a new object based on the data in the original. The data in the new object is incremented, but the data in the original is left unchanged. Although this works, the compiler translation of the increment operator results in a new object being created each time it is used. This can be expensive in terms of memory use and garbage collection overhead. Therefore, it is recommended that you limit operator overloads when you define types. This recommendation applies to all operators, not just to the increment operator.

Defining Operator Pairs

Some operators naturally come in pairs. For example, if you can compare two Hour values by using the != operator, you would expect to be able to also compare two Hour values by using the == operator. The C# compiler enforces this very reasonable expectation by insisting that if you define either operator == or operator !=, you must define them both. This neither-or-both rule also applies to the < and > operators and the <= and >= operators. The C# compiler does not write any of these operator partners for you. You must write them all explicitly yourself, regardless of how obvious they might seem. Here are the == and != operators for the Hour structure:

struct Hour

{

public Hour(int initialValue)

{

this.value = initialValue;

}

...

public static bool operator ==(Hour lhs, Hour rhs)

{

return lhs.value == rhs.value;

}

public static bool operator !=(Hour lhs, Hour rhs)

{

return lhs.value != rhs.value;

}

...

private int value;

}

The return type from these operators does not actually have to be Boolean. However, you would need to have a very good reason for using some other type, or these operators could become very confusing!

NOTE

If you define operator == and operator != in a class, you should also override the Equals and GetHashCode methods inherited from System.Object (or System.ValueType if you are creating a structure). The Equals method should exhibit exactly the same behavior as operator ==. (You should define one in terms of the other.) The GetHashCode method is used by other classes in the Microsoft .NET Framework. (When you use an object as a key in a hash table, for example, the GetHashCode method is called on the object to help calculate a hash value. For more information, see the .NET Framework reference documentation supplied with Visual Studio 2012.) All this method needs to do is return a distinguishing integer value. (Don’t return the same integer from theGetHashCode method of all your objects, however, because this will nullify the effectiveness of the hashing algorithms.)

Implementing Operators

In the following exercise, you will develop a class that simulates complex numbers.

A complex number has two elements: a real component and an imaginary component. Typically, a complex number is represented in the form (x + yi), where x is the real component and yi is the imaginary component. The values of x and y are regular integers, and i represents the square root of –1 (hence the reason why yi is imaginary). Despite their rather obscure and theoretical feel, complex numbers have a large number of uses in the fields of electronics, applied mathematics, and physics, and in many aspects of engineering. If you want more information about how and why complex numbers are useful, Wikipedia provides a useful and informative article.

NOTE

The .NET Framework version 4.0 and later includes a type called Complex in the System.Numerics namespace that implements complex numbers, so there is no real need to define your own version of this type any more. However, it is still instructive to see how to implement some of the common operators for this type.

You will implement complex numbers as a pair of integers that represent the coefficients x and y for the real and imaginary elements. You will also implement the operands necessary for performing simple arithmetic using complex numbers. The following table summarizes how to perform the four primary arithmetic operations on a pair of complex numbers, (a + bi) and (c + di).

Operation

Calculation

(a + bi) + (c + di)

((a + c) + (b + d)i)

(a + bi) – (c + di)

((a – c) + (b – d)i)

(a + bi) * (c + di)

(( a * c – b * d) + (b * c + a * d)i)

(a + bi) / (c + di)

((( a * c + b * d) / ( c * c + d * d)) + (( b * c - a * d) / ( c * c + d * d))i)

Create the Complex class and implement the arithmetic operators

1. Start Microsoft Visual Studio 2012 if it is not already running.

2. Open the ComplexNumbers project, located in the \Microsoft Press\Visual CSharp Step By Step\Chapter 22\Windows X\ComplexNumbers folder in your Documents folder. This is a console application that you will use to build and test your code. The Program.cs file contains the familiar doWork method.

3. In Solution Explorer, click the ComplexNumbers project. On the PROJECT menu, click Add Class. In the Add New Item – Complex Numbers dialog box, type Complex.cs in the Name text box, and then click Add.

Visual Studio creates the Complex class and opens the Complex.cs file in the Code and Text Editor window.

4. Add the automatic integer properties Real and Imaginary to the Complex class, as shown below in bold. You will use these two properties to hold the real and imaginary components of a complex number.

5. class Complex

6. {

7. public int Real { get; set; }

8. public int Imaginary { get; set; }

}

9. Add the constructor shown next in bold to the Complex class. This constructor takes two int parameters and uses them to populate the Real and Imaginary properties.

10.class Complex

11.{

12. ...

13. public Complex (int real, int imaginary)

14. {

15. this.Real = real;

16. this.Imaginary = imaginary;

17. }

}

18.Override the ToString method as shown next in bold. This method returns a string representing the complex number in the form (x + yi).

19.class Complex

20.{

21. ...

22. public override string ToString()

23. {

24. return String.Format("({0} + {1}i)", this.Real, this.Imaginary);

25. }

}

26.Add the overloaded + operator shown below in bold to the Complex class. This is the binary addition operator. It takes two Complex objects and adds them together by performing the calculation shown in the table at the start of the exercise. The operator returns a new Complex object containing the results of this calculation.

27.class Complex

28.{

29. ...

30. public static Complex operator +(Complex lhs, Complex rhs)

31. {

32. return new Complex(lhs.Real + rhs.Real, lhs.Imaginary + rhs.Imaginary);

33. }

}

34.Add the overloaded operator to the Complex class. This operator follows the same form as the overloaded + operator.

35.class Complex

36.{

37. ...

38. public static Complex operator -(Complex lhs, Complex rhs)

39. {

40. return new Complex(lhs.Real - rhs.Real, lhs.Imaginary - rhs.Imaginary);

41. }

}

42.Implement the * operator and / operator. These two operators follow the same form as the previous two operators, although the calculations are a little more complicated. (The calculation for the / operator has been broken down into two steps to avoid lengthy lines of code.)

43.class Complex

44.{

45. ...

46. public static Complex operator *(Complex lhs, Complex rhs)

47. {

48. return new Complex(lhs.Real * rhs.Real - lhs.Imaginary * rhs.Imaginary,

49. lhs.Imaginary * rhs.Real + lhs.Real * rhs.Imaginary);

50. }

51.

52. public static Complex operator /(Complex lhs, Complex rhs)

53. {

54. int realElement = (lhs.Real * rhs.Real + lhs.Imaginary * rhs.Imaginary) /

55. (rhs.Real * rhs.Real + rhs.Imaginary * rhs.Imaginary);

56. int imaginaryElement = (lhs.Imaginary * rhs.Real - lhs.Real * rhs.Imaginary) /

57. (rhs.Real * rhs.Real + rhs.Imaginary * rhs.Imaginary);

58. return new Complex(realElement, imaginaryElement);

59. }

}

60.Display the Program.cs file in the Code and Text Editor window. Add the following statements shown in bold to the doWork method of the Program class and delete the // TODO: comment:

61.static void doWork()

62.{

63. Complex first = new Complex(10, 4);

64. Complex second = new Complex(5, 2);

65.

66. Console.WriteLine("first is {0}", first);

67. Console.WriteLine("second is {0}", second);

68.

69. Complex temp = first + second;

70. Console.WriteLine("Add: result is {0}", temp);

71.

72. temp = first - second;

73. Console.WriteLine("Subtract: result is {0}", temp);

74.

75. temp = first * second;

76. Console.WriteLine("Multiply: result is {0}", temp);

77.

78. temp = first / second;

79. Console.WriteLine("Divide: result is {0}", temp);

}

This code creates two Complex objects that represent the complex values (10 + 4i) and (5 + 2i). The code displays them and then tests each of the operators you have just defined, displaying the results in each case.

80.On the DEBUG menu, click Start Without Debugging.

Verify that the application displays the results shown in the following image.

image with no caption

81.Close the application, and return to the Visual Studio 2012 programming environment.

You have now created a type that models complex numbers and supports basic arithmetic operations. In the next exercise, you will extend the Complex class and provide the equality operators, == and !=.

Implement the equality operators

1. In Visual Studio 2012, display the Complex.cs file in the Code and Text Editor window.

2. Add the == and != operators to the Complex class as shown below in bold. Notice that these operators both make use of the Equals method. The Equals method compares an instance of a class against another instance specified as an argument. It returns true if they have equivalent values and false otherwise.

3. class Complex

4. {

5. ...

6. public static bool operator ==(Complex lhs, Complex rhs)

7. {

8. return lhs.Equals(rhs);

9. }

10.

11. public static bool operator !=(Complex lhs, Complex rhs)

12. {

13. return !(lhs.Equals(rhs));

14. }

}

15.On the BUILD menu, click Rebuild Solution.

The Error List window displays the following warning messages:

'ComplexNumbers.Complex' defines operator == or operator != but does not override

Object.Equals(object o)

'ComplexNumbers.Complex' defines operator == or operator != but does not override

Object.GetHashCode()

If you define the != and == operators, you should also override the Equals and GetHashCode methods inherited from System.Object.

NOTE

If the Error List window is not displayed, on the VIEW menu, click Error List.

16.Override the Equals method in the Complex class as shown below in bold:

17.class Complex

18.{

19. ...

20. public override bool Equals(Object obj)

21. {

22. if (obj is Complex)

23. {

24. Complex compare = (Complex)obj;

25. return (this.Real == compare.Real) &&

26. (this.Imaginary == compare.Imaginary);

27. }

28. else

29. {

30. return false;

31. }

32. }

}

The Equals method takes an Object as a parameter. This code verifies that the type of the parameter is actually a Complex object. If it is, this code compares the values in the Real and Imaginary properties in the current instance and the parameter passed in. If they are the same, the method returns true; it returns false otherwise. If the parameter passed in is not a Complex object, the method returns false.

IMPORTANT

It is tempting to write the Equals method like this:

public override bool Equals(Object obj)

{

Complex compare = obj as Complex;

if (compare != null)

{

return (this.Real == compare.Real) &&

(this.Imaginary == compare.Imaginary);

}

else

{

return false;

}

}

However, the expression compare != null invokes the != operator of the Complex class, which calls the Equals method again, resulting in a recursive loop.

33.Override the GetHashCode method. This implementation simply calls the method inherited from the Object class, but you can provide your own mechanism to generate a hash code for an object if you prefer.

34.Class Complex

35.{

36. ...

37. public override int GetHashCode()

38. {

39. return base.GetHashCode();

40. }

}

41.On the BUILD menu, click Rebuild Solution.

Verify that the solution now builds without reporting any warnings.

42.Display the Program.cs file in the Code and Text Editor window. Add the following code shown in bold to the end of the doWork method:

43.static void doWork()

44.{

45. ...

46. if (temp == first)

47. {

48. Console.WriteLine("Comparison: temp == first");

49. }

50. else

51. {

52. Console.WriteLine("Comparison: temp != first");

53. }

54.

55. if (temp == temp)

56. {

57. Console.WriteLine("Comparison: temp == temp");

58. }

59. else

60. {

61. Console.WriteLine("Comparison: temp != temp");

62. }

}

NOTE

The expression temp == temp generates a warning message, “Comparison made to same variable: did you mean to compare to something else?” In this case, you can ignore the warning because this comparison is intentional; it is to verify that the == operator is working as expected.

63.On the DEBUG menu, click Start Without Debugging. Verify that the final two messages displayed are these:

64.Comparison: temp != first

Comparison: temp == temp

65.Close the application, and return to Visual Studio 2012.

Understanding Conversion Operators

Sometimes you need to convert an expression of one type to another. For example, the following method is declared with a single double parameter:

class Example

{

public static void MyDoubleMethod(double parameter)

{

...

}

}

You might reasonably expect that only values of type double could be used as arguments when calling MyDoubleMethod, but this is not so. The C# compiler also allows MyDoubleMethod to be called with an argument of some other type, but only if the value of the argument can be converted to a double. For example, if you provide an int argument, the compiler generates code that converts the value of the argument to a double when the method is called.

Providing Built-in Conversions

The built-in types have some built-in conversions. For example, as mentioned previously, an int can be implicitly converted to a double. An implicit conversion requires no special syntax and never throws an exception:

Example.MyDoubleMethod(42); // implicit int-to-double conversion

An implicit conversion is sometimes called a widening conversion because the result is wider than the original value—it contains at least as much information as the original value, and nothing is lost. In the case of int and double, the range of double is greater than that of int, and all int values have an equivalent double value. However, the converse is not true, and a double value cannot be implicitly converted to an int:

class Example

{

public static void MyIntMethod(int parameter)

{

...

}

}

...

Example.MyIntMethod(42.0); // compile-time error

When you convert a double to an int, you run the risk of losing information, so the conversion will not be performed automatically. (Consider what would happen if the argument to MyIntMethod were 42.5—how should this be converted?) A double can be converted to an int, but the conversion requires an explicit notation (a cast):

Example.MyIntMethod((int)42.0);

An explicit conversion is sometimes called a narrowing conversion because the result is narrower than the original value (that is, it can contain less information) and may throw an OverflowException exception if the resulting value is out of the range of the target type. C# allows you to create conversion operators for your own user-defined types to control whether it is sensible to convert values to other types, and you can also specify whether these conversions are implicit or explicit.

Implementing User-Defined Conversion Operators

The syntax for declaring a user-defined conversion operator has some similarities to that for declaring an overloaded operator, but also some important differences. Here’s a conversion operator that allows an Hour object to be implicitly converted to an int:

struct Hour

{

...

public static implicit operator int (Hour from)

{

return from.value;

}

private int value;

}

A conversion operator must be public and it must also be static. The type you are converting from is declared as the parameter (in this case, Hour), and the type you are converting to is declared as the type name after the keyword operator (in this case, int). There is no return type specified before the keyword operator.

When declaring your own conversion operators, you must specify whether they are implicit conversion operators or explicit conversion operators. You do this by using the implicit and explicit keywords. For example, the Hour to int conversion operator mentioned earlier is implicit, meaning that the C# compiler can use it without requiring a cast:

class Example

{

public static void MyOtherMethod(int parameter) { ... }

public static void Main()

{

Hour lunch = new Hour(12);

Example.MyOtherMethod(lunch); // implicit Hour to int conversion

}

}

If the conversion operator had been declared explicit, the preceding example would not have compiled, because an explicit conversion operator requires a cast:

Example.MyOtherMethod((int)lunch); // explicit Hour to int conversion

When should you declare a conversion operator as explicit or implicit? If a conversion is always safe, does not run the risk of losing information, and cannot throw an exception, it can be defined as an implicit conversion. Otherwise, it should be declared as an explicit conversion. Converting from an Hour to an int is always safe—every Hour has a corresponding int value—so it makes sense for it to be implicit. An operator that converts a string to an Hour should be explicit because not all strings represent valid Hours. (The string “7” is fine, but how would you convert the string “Hello, World” to an Hour?)

Creating Symmetric Operators, Revisited

Conversion operators provide you with an alternative way to resolve the problem of providing symmetric operators. For example, instead of providing three versions of operator + (Hour + Hour, Hour + int, and int + Hour) for the Hour structure, as shown earlier, you can provide a single version of operator + (that takes two Hour parameters) and an implicit int to Hour conversion, like this:

struct Hour

{

public Hour(int initialValue)

{

this.value = initialValue;

}

public static Hour operator +(Hour lhs, Hour rhs)

{

return new Hour(lhs.value + rhs.value);

}

public static implicit operator Hour (int from)

{

return new Hour (from);

}

...

private int value;

}

If you add an Hour to an int (in either order), the C# compiler automatically converts the int to an Hour and then calls operator + with two Hour arguments:

void Example(Hour a, int b)

{

Hour eg1 = a + b; // b converted to an Hour

Hour eg2 = b + a; // b converted to an Hour

}

Writing Conversion Operators

In the following exercise, you will add further operators to the Complex class. You will start by writing a pair of conversion operators that convert between the int type and the Complex type. Converting an int to a Complex object is always a safe process and never loses information (because an int is really just a Complex number without an imaginary element). So you will implement this as an implicit conversion operator. However, the converse is not true—to convert a Complex object into an int, you have to discard the imaginary element. So you will implement this conversion operator as explicit.

Implement the conversion operators

1. Return to Visual Studio 2012 and display the Complex.cs file in the Code and Text Editor window. Add the constructor shown below in bold to the Complex class. This constructor takes a single int parameter, which it uses to initialize the Real property. The Imaginary property is set to0.

2. class Complex

3. {

4. ...

5. public Complex(int real)

6. {

7. this.Real = real;

8. this.Imaginary = 0;

9. }

10. ...

}

11.Add the following implicit conversion operator to the Complex class. This operator converts from an int to a Complex object by returning a new instance of the Complex class built using the constructor you created in the previous step.

12.class Complex

13.{

14. ...

15. public static implicit operator Complex(int from)

16. {

17. return new Complex(from);

18. }

}

19.Add the following explicit conversion operator shown in bold to the Complex class. This operator takes a Complex object and returns the value of the Real property. This conversion discards the imaginary element of the complex number.

20.class Complex

21.{

22. ...

23. public static explicit operator int(Complex from)

24. {

25. return from.Real;

26. }

}

27.Display the Program.cs file in the Code and Text Editor window. Add the following code shown in bold to the end of the doWork method:

28.static void doWork()

29.{

30. ...

31. Console.WriteLine("Current value of temp is {0}", temp);

32.

33. if (temp == 2)

34. {

35.

36. Console.WriteLine("Comparison after conversion: temp == 2");

37. }

38. else

39. {

40. Console.WriteLine("Comparison after conversion: temp != 2");

41. }

42.

43. temp += 2;

44. Console.WriteLine("Value after adding 2: temp = {0}", temp);

}

These statements test the implicit operator that converts an int to a Complex object. The if statement compares a Complex object to an int. The compiler generates code that converts the int into a Complex object first and then invokes the == operator of the Complex class. The statement that adds 2 to the temp variable converts the int value 2 into a Complex object and then uses the + operator of the Complex class.

45.Add the following statements to end of the doWork method:

46.static void doWork()

47.{

48. ...

49. int tempInt = temp;

50. Console.WriteLine("Int value after conversion: tempInt == {0}", tempInt);

}

The first statement attempts to assign a Complex object to an int variable.

51.On the BUILD menu, click Rebuild Solution.

The solution fails to build, and the compiler reports the following error in the Error List window:

Cannot implicitly convert type 'ComplexNumbers.Complex' to 'int'. An explicit

conversion exists (are you missing a cast?)

The operator that converts from a Complex object to an int is an explicit conversion operator, so you must specify a cast.

52.Modify the statement that attempts to store a Complex value in an int variable to use a cast, like this:

int tempInt = (int)temp;

53.On the DEBUG menu, click Start Without Debugging. Verify that the solution now builds and that the final four messages displayed look like this:

54.Current value of temp is (2 + 0i)

55.Comparison after conversion: temp == 2

56.Value after adding 2: temp = (4 + 0i)

Int value after conversion: tempInt == 4

57.Close the application, and return to Visual Studio 2012.

Summary

In this chapter, you learned how to overload operators and provide functionality specific to a class or structure. You implemented a number of common arithmetic operators, and you also created operators that enable you to compare instances of a class. Finally, you learned how to create implicit and explicit conversion operators.

§ If you want to continue to the next chapter

Keep Visual Studio 2012 running, and turn to Chapter 23.

§ If you want to exit Visual Studio 2012 now

On the FILE menu, click Exit. If you see a Save dialog box, click Yes and save the project.

Chapter 22 Quick Reference

To

Do this

Implement an operator

Write the keywords public and static, followed by the return type, followed by the operator keyword, followed by the operator symbol being declared, followed by the appropriate parameters between parentheses. Implement the logic for the operator in the body of the method. For example:

class Complex

{

...

public static bool operator==(Complex lhs, Complex rhs)

{

... // Implement logic for == operator

}

...

}

Define a conversion operator

Write the keywords public and static, followed by the keyword implicit or explicit, followed by the operator keyword, followed by the type being converted to, followed by the type being converted from as a single parameter between parentheses. For example:

class Complex

{

...

public static implicit operator Complex(int from)

{

... // code to convert from an int

}

...

}

Part IV. Building Professional Windows 8 Applications with C#

The first three parts of this book focused on how to use C# to build applications and components. You now have a thorough grounding in the syntax and semantics of the C# language, so it is time to move on and examine how you can use this knowledge to take advantage of the features that Microsoft Windows 8 provides for building professional, responsive, great-looking applications.

With some minor exceptions, the content presented so far has been largely independent of the version of Windows you are using; all the examples and exercises have been tested and verified against Microsoft Windows 7 and Windows 8. The only requirement is that you have Microsoft Visual Studio 2012 and the Microsoft .NET Framework version 4.5 installed on your computer. While Windows 7 provides a powerful platform for running software on a range of hardware, from high-end servers through desktop machines and laptop computers, Windows 8 has been designed specifically to take advantage of the next generation of mobile devices, and many users running Windows 8 software will likely be doing so on a tablet computer or smartphone. Consequently, the consumer version of Windows 8 has been optimized to support the styles of applications that users are likely to run in this environment. In particular, Windows 8 can receive input from the user through a touch-sensitive screen, and it is also aware of the location and orientation of the device on which it is running (if the hardware provides the appropriate positional sensors and accelerometers). The networking capabilities of Windows 8 enable you to build roaming, cloud-connected applications—applications that are not tied to a specific computer but that can follow users when they sign in on another device. The new style of user interface implemented by Windows 8 provides a framework for constructing compelling, interactive applications that can incorporate all of these features. Additionally, the Windows 8 graphical interface is built on top of Direct3D hardware, accessed by using the DirectX APIs, and the operating system provides libraries, enabling you to build fast and fluid graphical applications and games. In short, Windows 8 is intended to provide the platform for highly mobile, highly graphical, highly connected applications. Developers can make these applications publicly available by publishing them to the Windows Store. Therefore, applications that use the new model defined by Windows 8 are called Windows Store apps.

It would be remiss of a book such as this not to provide an introduction to building applications that use the specific features of Windows 8, and that is the purpose of this final part (the previous edition of this book covered many of the equivalent topics for Windows 7, concentrating on building Windows Presentation Foundation applications). While some of the content in Chapter 23 and Chapter 24 is applicable to Windows 7, the primary focus of this section is on building Windows 8 applications. In this part, you will be introduced to the asynchronous model of programming initially developed as part of the .NET Framework 4.0, now extended in the .NET Framework 4.5 and integrated into C# through the async and await keywords. You will also learn a lot more about the templates provided with Visual Studio 2012 to help you build Windows 8 applications and how to use these templates to format and display data in an intuitive manner. Finally, you will see how to build a Windows 8 application that uses the new features of the operating system and user interface to retrieve and present complex information in a natural and easily navigable style.