Understanding Conversion Operators Sometimes it is necessary to convert an expression of one type into 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 whose type is not double, but only as long as that value can be converted to a double. The compiler will generate code that performs this conversion when the method is called. Providing Built-In Conversions The built-in types have some built-in conversions. For example, 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, as the result is wider than the original value—it contains at least as much information as the original value, and nothing is lost. On the other hand, a double cannot be implicitly converted to an int: class Example { public static void MyIntMethod(int parameter) { } } Example.MyIntMethod(42.0); // compile-time error Converting from a double to an int runs the risk of losing information, so it will not be done automatically (consider what would happen if the argument to MyIntMethod was 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 as the result is narrower than the original value (it can contain less information), and can throw an OverflowException. C# allows you to provide conversion operators for your own user- defined types to control whether they can be implicitly or explicitly converted to other types. Implementing User-Defined Conversion Operators The syntax for declaring a user-defined conversion operator is similar to an overloaded operator. A conversion operator must be public and must also be static. Here's a conversion operator that allows an Hour object to be implicitly converted into an int: struct Hour { public static implicit operator int (Hour from) { return this.value; } private int value; } The type you are converting from is declared as the single 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 previously is implicit, meaning that the C# compiler can use it implicitly (without a cast): class Example { public static void Method(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 previous example would not have compiled because an explicit conversion operator requires an explicit 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, then 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 converted a string to an Hour should be explicit, as not all strings represent valid Hours. (While the string “7” is fine, how would you convert the string “Hello, World” into an Hour?) Creating Symmetric Operators Revisited Conversion operators provide you with an alternate 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 struct 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 and 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 } Adding an Implicit Conversion Operator In the following exercise, you will modify the digital clock application from the previous exercise. You will add an implicit conversion operator to the Second struct and remove the operators that it replaces. Write the conversion operator 1. Return to Visual Studio 2005 displaying the Operators project. Display the Clock.cs file in the Code and Text Editor window and examine the tock method again: 2. private void tock() 3. { 4. this.second++; 5. if (this.second == 0) 6. { 7. this.minute++; 8. if (this.minute == 0) 9. { 10. this.hour++; 11. } 12. } } Notice the statement if (this.second == 0). This fragment of code compares a Second to an int using the == operator. 13. In the Code pane, open the Second.cs source file. The Second struct currently contains three overloaded implementations of operator== and three overloaded implementations of operator!=. Each operator is overloaded for the parameter type pairs (Second, Second), (Second, int), and (int, Second). 14. In the Code and Text Editor window, delete the versions of operator== and operator!= that take one Second and one int parameter (do not delete the operators that take two Second parameters). The following three operators should be the only operators in the Second struct (there will be other methods, however): 15. struct Second 16. { 17. 18. public static bool operator==(Second lhs, Second rhs) 19. { 20. return lhs.value == rhs.value; 21. } 22. 23. public static bool operator!=(Second lhs, Second rhs) 24. { 25. return lhs.value != rhs.value; 26. } 27. } 28. On the Build menu, click Build Solution. The build fails with the message: Operator '==' cannot be applied to the operands of type 'Operators.Second' and 'int' Removing the operators that compare a Second and an int cause the statement highlighted earlier to fail to compile. 29. In the Code and Text Editor window, add an implicit conversion operator to the Second struct that converts from an int to a Second. The conversion operator should look like this: struct Second { public static implicit operator Second (int arg) { return new Second(arg); } } 30. On the Build menu, click Build Solution and correct any errors. The program successfully builds this time because the conversion operator and the remaining two operators together provide the same functionality as the four deleted operator overloads. The only difference is that using an implicit conversion operator is potentially a little slower than not using an implicit conversion operator. 31. On the Debug menu, click Start Without Debugging. Verify that the application still runs. 32. Close the application and return to the Visual Studio 2005 programming environment. • If you want to continue to the next chapter Keep Visual Studio 2005 running and turn to Chapter 20. • If you want to exit Visual Studio 2005 now On the File menu, click Exit. If you see a Save dialog box, click Yes. . MyDoubleMethod, but this is not so. The C# compiler also allows MyDoubleMethod to be called with an argument whose type is not double, but only as long as that value can be converted to a double. . 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. lost. On the other hand, a double cannot be implicitly converted to an int: class Example { public static void MyIntMethod(int parameter) { } } Example.MyIntMethod(42.0); // compile-time