Operator Overloading

   


You have already seen how the + operator can have different meanings in different contexts. For example, when we position the + operator between two values of type int, as in the following:

 int distance1, distance2; ... totalDistance = distance1 + distance2; 

the compiler will recognize the int types and perform the familiar addition operation we know from arithmetic. However, if we apply the + operator with two values of type string:

 string myText, yourText, fullText; myString = "The boy was "; yourString = "walking in the park"; fullText = myString + yourString; 

myString and yourString are concatenated and assigned to fullText, which contains the following text:

 "The boy was walking in the park" 

So in the latter case the compiler performs another familiar but very different operation called concatenation, which combines the two texts in a meaningful way. Because the + operator can be applied to values of the two different types int and string, and in each case have different semantics, we say that the + operator is overloaded.

Note

graphics/common.gif

An overloaded operator is an operator symbol that can be applied to different values of different types.


The operator overloading concept was conceived to make the code look more natural and to make expressions more compact and easier to write. To illustrate, let's pretend for the moment that operator overloading is unavailable in C#. The C# designers might then decide to reserve the + operator for adding int values together, thereby making it unavailable for any other simple type. Now, the designers would have to equip each simple type with a method called say, Add, that would take two arguments of the corresponding type and return the result to the caller. Adding two double values together would then look like the second line of the following code:

 double distance1, distance2; totalDistance = Double.Add(distance1, distance2); 

which is less intuitive and more cumbersome than the natural and compact syntax we are used to.

 totalDistance = distance1 + distance2; 

Apart from the overloaded + operator, C# contains many other built-in cases of operator overloading. Implementing operator overloading, however, is not confined to the designers of C#. You can also implement it in your own classes and thereby allow them to be used with operator syntax. The next section looks more closely at this possibility.

User-Defined Operator Overloading

When you use a property or an indexer, you are in reality, behind the convenient syntax, triggering a block of statements to be executed (either in a get or set statement block). Operator overloading follows the same principle. When you apply an overloaded operator to one object (with a unary operator) or two objects (with a binary operator), you are calling a method behind this syntax, which contains statements that make it seem as if it is the operator itself that provides the expected result.

Operator Overloading Is Syntactic Sugar

graphics/common.gif

Operator overloading is just another (often convenient) way of calling a method and is, for that reason, frequently referred to as syntactic sugar.


To implement operator overloading in your class, you must write this underlying method (called the operator method) just as you wrote the underlying get and set statement blocks for the properties and indexers. Before we look at how this is done, let's have a look at the TimeSpan class (lines 3 53) in Listing 14.6. The TimeSpan class is an obvious candidate for operator overloading, but does not yet support it. An object of TimeSpan represents a time interval.

Listing 14.6 TimeSpanNoOverloading.cs
01: using System; 02: 03: class TimeSpan 04: { 05:     private uint totalSeconds; 06:     private const uint SecondsInHour = 3600; 07:     private const uint SecondsInMinute = 60; 08: 09:     public TimeSpan() 10:     { 11:         totalSeconds = 0; 12:     } 13: 14:     public TimeSpan(uint initialHours, uint initialMinutes, 15:         uint initialSeconds) 16:     { 17:         totalSeconds = initialHours * SecondsInHour + 18:             initialMinutes * SecondsInMinute + initialSeconds; 19:     } 20: 21:     public uint Seconds 22:     { 23:         get 24:         { 25:             return totalSeconds; 26:         } 27:         set 28:         { 29:             totalSeconds = value; 30:         } 31:     } 32: 33:     public void PrintHourMinSec() 34:     { 35:         uint hours; 36:         uint minutes; 37:         uint seconds; 38: 39:         hours = totalSeconds / SecondsInHour; 40:         minutes = (totalSeconds % SecondsInHour) / SecondsInMinute; 41:         seconds = (totalSeconds % SecondsInHour) % SecondsInMinute; 42:         Console.WriteLine("{ 0}  Hours  { 1}  Minutes  { 2}  Seconds", 43:             hours, minutes, seconds); 44:     } 45: 46:     public static TimeSpan Add(TimeSpan timeSpan1, TimeSpan timeSpan2) 47:     { 48:         TimeSpan sumTimeSpan = new TimeSpan(); 49: 50:         sumTimeSpan.Seconds = timeSpan1.Seconds + timeSpan2.Seconds; 51:         return sumTimeSpan; 52:     } 53: } 54: 55: class TimeSpanTest 56: { 57:     public static void Main() 58:     { 59:         TimeSpan totalTime; 60:         TimeSpan myTime = new TimeSpan(1,20,30); 61:         TimeSpan yourTime = new TimeSpan(2,40,45); 62: 63:         totalTime = TimeSpan.Add(myTime, yourTime); 64: 65:         Console.Write("My time:    "); 66:         myTime.PrintHourMinSec(); 67:         Console.Write("Your time:  "); 68:         yourTime.PrintHourMinSec(); 69:         Console.Write("Total time: "); 70:         totalTime.PrintHourMinSec(); 71:     } 72: } My time:    1 Hours  20 Minutes  30 Seconds Your time:  2 Hours  40 Minutes  45 Seconds Total time: 4 Hours  1 Minutes  15 Seconds 

The time interval of each TimeSpan object is held by its instance variable totalSeconds (line 5). When a new object is created, the provided duration in hours, minutes, and seconds is converted into a single value and assigned to totalSeconds in the TimeSpan constructor (lines 14 19).Two TimeSpan objects will often need to be added together, so I have included a static method called Add (lines 46 52) to provide this functionality. Add specifies two formal parameters in its header called timeSpan1 and timeSpan2. In line 50, the totalSeconds of each of these two objects are added together and their sum is assigned to the totalSeconds of the new TimeSpan object called sumTimeSpan, which is returned to the caller in line 51.

Line 63 of the Main() method (lines 57 71) is used to test the Add method. Notice in the sample output how the tedious task of adding different times together, consisting of hours, minutes and seconds, is correctly handled by Add.

The TimeSpan class and its Add method perform their jobs correctly, but wouldn't it be more intuitive and convenient if we could add the totalSeconds of two TimeSpan objects together by using the following syntax instead of the syntax shown in line 63 of Listing 14.6?

 totalTime = myTime + yourTime; 

We can support this syntax with operator overloading simply by substituting the word Add in the method header of the Add method (line 46, Listing 14.6) with the keyword operator followed by +, as shown in Figure 14.3, while the rest of the method remains untouched. This new method is commonly called an operator+ method.

Figure 14.3. Changing the Add method to an operator method.
graphics/14fig03.gif

The new operator+ method gives the compiler precise instructions on how to execute the following expression:

 <Object1_Of_Type_TimeSpan> + <Object2_Of_Type_TimeSpan> 

Whenever it encounters an expression with this fingerprint, consisting of two TimeSpan objects surrounding a plus symbol, it will execute our newly created operator+ method. Just prior to executing its statements, it assigns the reference of the TimeSpan object that is positioned on the left side of the + symbol to the timeSpan1 parameter of the operator+ method (see 2 in Figure 14.4). Similarly, timeSpan2 will be assigned the reference of the TimeSpan object on the right side of the + symbol. After the operator+ method has terminated, the original expression takes the value returned from the operator+ method (see 3 in Figure 14.4).

Figure 14.4. Executing two TimeSpan objects surrounding a + symbol.
graphics/14fig04.gif

The general syntax for declaring an operator method is shown in Syntax Box 14.3. In general, the operator method looks like any public static (it must be public static) method with its name substituted by the keyword operator followed by an operator symbol. The return type cannot be void, and you must specify either one or two formal parameters, depending on whether you are overloading a unary or a binary operator. For more detailed information regarding the declaration, please refer to the notes in Syntax Box 14.3.

Syntax Box 14.3 Operator Method Declaration

 Operator_method_declaration::= public static <Return_Type> operator <Operator_symbol> <Type> <Formal_parameter1> graphics/ccc.gif [, <Type> <Formal_parameter2>] ) {     <Statements> } 

where

 <Operator_symbol>                  ::=  (one of the unary operators: ) +  -  !  ~   graphics/ccc.gif++  --  !                                                      true  false                  ::= (one of the binary operators: ) +  -  *  /   graphics/ccc.gif%  &  |  ^                      <<  >>  ==  !=   <   >   <=   >= 

Notes:

  • Only the specific operators shown here can be overloaded. You cannot create your own operator symbol, such as <+, to be overloaded.

  • The <Return_type> cannot be void and, as a consequence, the <Statements> in the method body must use the return keyword to return a value of a type that matches <Return_type>.

  • The <Return_type> can be any type (apart from void), but the operators true and false, along with all the comparison operators, should always return a Boolean type value. The other operators should, as a rule of thumb, always return a type that is identical to the type (class) in which the operator method is residing.

  • The operator method must have either one or two formal parameters in its formal parameter list. If a unary operator is overloaded, exactly one formal parameter must be specified; if a binary operator is overloaded, exactly two formal parameters must be overloaded.

  • If the overloaded operator is a unary operator, the single formal parameter must be of the same type as the type (class) in which the operator method is residing.

  • If the overloaded operator is a binary operator, the first formal parameter must be of the same type as the type (class) in which the operator method is residing; the second formal parameter can be of any type.

  • The operator method must always be declared public and static.

  • If you overload one of the comparison operators (<, <=, or ==), you must also overload its matching counterpart (>, >=, or !=).

  • You cannot overload the cast operator (<Type>) in this context. However, the next section describes a technique similar to cast operator overloading (had it been possible) called conversion operators.

  • An operator's precedence and associativity cannot be changed.

  • It is not possible to turn a unary operator into a binary operator or vice versa.

The programmers that utilize our TimeSpan class enjoy the new intuitive TimeSpan addition syntax. However, they further request the ability to compare two TimeSpan objects with each of the operators <, >, ==, and != and to increment an object's totalSeconds instance variable by one with the ++ operator. So we go back to the drawing board to create a new improved TimeSpan class with the desired capabilities.

Before implementing the operator methods that will allow this extra syntax, it is important to define the meaning of <, >, ==, and != when applied to two TimeSpan objects as in the following:

 <TimeSpanObject1> <Comparison_operator> <TimeSpanObject2> 

In our case, the two TimeSpan objects being compared are defined to have their totalSeconds instance variables compared by the specified operator. For example, the operator< method will return the result of the following comparison (see line 69 of Listing 14.7).

 <TimeSpanObject1>.Seconds  <  <TimeSpanObject2>.Seconds 

The resulting improved TimeSpan class is shown in lines 3 81 of Listing 14.7.

Listing 14.7 TimeSpanWithOverloading.cs
 01: using System;  02:  03: class TimeSpan  04: {  05:     private uint totalSeconds;  06:     private const uint SecondsInHour = 3600;  07:     private const uint SecondsInMinute = 60;  08:  09:     public TimeSpan()  10:     {  11:         totalSeconds = 0;  12:     }  13:  14:     public TimeSpan(uint initialHours, uint initialMinutes,  15:         uint initialSeconds)  16:     {  17:         totalSeconds = initialHours * SecondsInHour +  18:             initialMinutes * SecondsInMinute + initialSeconds;  19:     }  20:  21:     public uint Seconds  22:     {  23:         get  24:         {  25:             return totalSeconds;  26:         }  27:         set  28:         {  29:             totalSeconds = value;  30:         }  31:     }  32:  33:     public void PrintHourMinSec()  34:     {  35:         uint hours;  36:         uint minutes;  37:         uint seconds;  38:  39:         hours = totalSeconds / SecondsInHour;  40:         minutes = (totalSeconds % SecondsInHour) / SecondsInMinute;  41:         seconds = (totalSeconds % SecondsInHour) % SecondsInMinute;  42:         Console.WriteLine("{ 0}  Hours  { 1}  Minutes  { 2}  Seconds",  43:             hours, minutes, seconds);  44:     }  45:   46:     public static TimeSpan operator+ (TimeSpan timeSpan1, TimeSpan timeSpan2)  47:     {  48:         TimeSpan sumTimeSpan = new TimeSpan();  49:  50:         sumTimeSpan.Seconds = timeSpan1.Seconds + timeSpan2.Seconds;  51:         return sumTimeSpan;  52:     }  53:  54:     public static TimeSpan operator++ (TimeSpan timeSpan1)  55:     {  56:         TimeSpan timeSpanIncremented = new TimeSpan();  57:  58:         timeSpanIncremented.Seconds = timeSpan1.Seconds + 1;  59:         return timeSpanIncremented;  60:     }  61:  62:     public static bool operator> (TimeSpan timeSpan1, TimeSpan timeSpan2)  63:     {  64:         return (timeSpan1.Seconds > timeSpan2.Seconds);  65:     }  66:  67:     public static bool operator< (TimeSpan timeSpan1, TimeSpan timeSpan2)  68:     {  69:         return (timeSpan1.Seconds < timeSpan2.Seconds);  70:     }  71:   72:     public static bool operator== (TimeSpan timeSpan1, TimeSpan timeSpan2)  73:     {  74:         return (timeSpan1.Seconds == timeSpan2.Seconds);  75:     }  76:  77:     public static bool operator!= (TimeSpan timeSpan1, TimeSpan timeSpan2)  78:     {  79:         return (timeSpan1.Seconds != timeSpan2.Seconds);  80:     }  81: }  82:  83: class TimeSpanTest  84: {  85:     public static void Main()  86:     {  87:         TimeSpan someTime;  88:         TimeSpan totalTime = new TimeSpan();  89:         TimeSpan myTime = new TimeSpan(2,40,45);  90:         TimeSpan yourTime = new TimeSpan(1,20,30);  91:  92:         totalTime += yourTime;  93:         totalTime += myTime;  94:  95:         Console.Write("Your time:       ");  96:         yourTime.PrintHourMinSec();  97:         Console.Write("My time:         ");  98:         myTime.PrintHourMinSec();  99:         Console.Write("Total race time: "); 100:         totalTime.PrintHourMinSec(); 101: 102:         if (myTime > yourTime) 103:             Console.WriteLine("\nI spent more time than you did"); 104:         else 105:             Console.WriteLine("\nYou spent more time than I did"); 106:  107:         myTime++; 108:         ++myTime; 109: 110:         Console.Write("\nMy time after two increments: "); 111:         myTime.PrintHourMinSec(); 112: 113:         someTime = new TimeSpan(1,20,30); 114:         if (yourTime == someTime) 115:             Console.WriteLine("\nSpan of someTime is equal to span of yourTime"); 116:         else 117:             Console.WriteLine("\nSpan of someTime is NOT equal to span of yourTime"); 118:     } 119: } 

Note

graphics/common.gif

When you compile Listing 14.7, you will likely see the following compiler warnings:

 TimeSpanWithOverLoading.cs(3,7): warning CS0660: 'TimeSpan' defines operator == or operator != but does not override Object.Equals(object o) TimeSpanWithOverLoading.cs(3,7): warning CS0660: 'TimeSpan' defines operator == or operator != but does not override Object.Equals(object o) 

These warnings are related to inheritance and method overriding, which we have not yet discussed. You can ignore these warnings for now and run the program without problems. Inheritance and method overriding is discussed in Chapter 16, "Inheritance Part I: Basic Concepts," and Chapter 17, "Inheritance Part II: abstract Functions, Polymorphism, and Interfaces."


 Your time:       1 Hours  20 Minutes  30 Seconds My time:         2 Hours  40 Minutes  45 Seconds Total race time: 4 Hours  1 Minutes  15 Seconds I spent more time than you did My time after two increments: 2 Hours  40 Minutes  47 Seconds Span of someTime is equal to span of yourTime 

The operator> method (lines 62 65) enables us to compare two TimeSpan objects with the > operator. The method (see line 64) closely follows the definitions put forth earlier for what we mean by comparing two TimeSpan methods. It has a bool return type instead of the TimeSpan type of the operator+ method, to match the bool value returned from line 64. The operator< method follows the same logic as the operator> method.

The ability to compare two TimeSpan objects with the == and != operators is achieved through the operator== method (lines 72 75) and the operator!= method (lines 77 80) in a similar fashion to that of the operator< and > methods.

In contrast to the four previously mentioned operator methods, the operator++ method (lines 54 60) only has one formal parameter because it is a unary operator. As prescribed in Syntax Box 14.3, the formal parameter is of the same type as the class in which it resides (TimeSpan). Observe in lines 107 and 108 of the Main method that the operator++ method allows us to apply both the post- and pre-increment syntax version of the ++ operator.

Similarly, when we specify the operator+ method, we can also use the combination assignment operator +=, as demonstrated in lines 92 and 93.

Recall that generally when two standard objects (except for objects of type string) are compared with the == operator (and the == operator has not been overloaded for these objects), their references and not their internal instance variables are compared. Had we not included the operator== method, the condition yourTime == someTime of line 114 would have been false instead of true, because the two yourTime and someTime objects that are being compared are situated in different locations and have different references.

Only Use Operator Overloading When It Is Natural and Makes the Program Clearer

graphics/common.gif

Most people intuitively understand the meaning of the following line:

 totalTime = myTime + yourTime; 

but what about the next line, where combinedPlanet, venus, and mars all represent objects of class Planet?

 combinedPlanet = venus + mars; 

Are we adding together the volumes of the two planets, their circumference, their distance from the sun, or perhaps one of the other numerous instance variables that could be used to describe a planet (or perhaps it is the programmer's way of expressing eternal peace between the two sexes)? Nobody, apart perhaps the programmer who originally included the operator+ method (who might have forgotten this six months later) in the Planet class, knows this answer intuitively. Instead of making the program clearer, it becomes cryptic and hard to read. The line would be much clearer if we applied a conventional static method with a visible name, as in the following line:

 combinedPlanet = Planet.AddVolumes(venus, mars); 



   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net