Operators in C


Operators in C#

Operators are the symbols and characters used to perform operations between one or more types. Consider the + operator, which can be used to add two numbers together and return the result. The + operator can also be used to add two strings together and return one concatenated string.

The behavior of an operator is defined at type level. This is called operator overloading because we are overloading an operator to exhibit specific behavior for a specific type.

The behavior of an operator on a type-by-type basis is contained within an operator overload method. Like all other types of method, this is an encapsulated section of code that performs a specific task. In the case of an operator overload method, however, the method contains the necessary logic and behavior that will be applied when an operator is used on a specific type. Operators can also therefore, be used to encapsulate expressions.

Operators are in Fact Expressions

The CLR has no knowledge of operators. When an operator is used, the C# language compiler actually generates the necessary MSIL code to perform the operation. Operators are treated and actually compiled into MSIL code as specialized methods. The MSIL code emitted by the C# compiler for operators will be examined later in this section.

Many types in the .NET Framework support operators; these types are typically primitive value types such as numbers and dates. For each of these types, decisions were made on which operators would be supported and what their behavior would be. When designing your own types, the same process must be followed. The class designer must consider which operators are relevant and what behavior each operator should exhibit for each custom type.

Operator overloading might be familiar to C++ developers; however, developers switching from Java and Visual Basic, including Visual Basic .NET might be new to operator overloading. This section will answer both how and when to use operator overloading.

In this chapter we will be looking at how operators can be defined for your own custom types. Although, these will primarily be classes, operators have an increased relevance with structures. We will examine the reasons for this within this section. The following table lists all of the operators that can be overloaded:

Type of Operator

Operators

Description

Unary Operators

+, , !, ~, ++, −−, true, false

Unary operators take single arguments, for instance:
i++;

Binary Operators

+, , *, /, %, &, |, ^, <<, >>

Binary operators take two arguments, for instance:
int x = 7;
int z = x + y;

Comparison Operators

==, !=, <, >, <=, >=

Comparison operators always take two arguments and return a Boolean result, for instance:
int a = 7;
int b = 10;
if (a > b){}
Also note that comparison operators must be overloaded in pairs – for instance if > is overloaded < must be overloaded too.

Note that Conditional Logical Operators && and ||, Array indexing Operators [], Cast Operators (), Assignment Operators = and += and other operators such as new and typeof cannot be overloaded.

Before we launch into the details of operator overloading, consider the following:

  • New operators cannot be defined. The list of operators available for overloading is finite and listed in the above table.

  • Operators have precedence. When operators are used in expressions we have to remember that the multiply operator (*) has a higher precedence that the addition operator (+), and so on.

Operator Overloading Syntax

We will look at an example with an Employee type, which represents an Employee with Name and Salary attributes. We also have a Bonus type, which represents an annual bonus. This type has an Amount attribute. In our application, we need to award an Employee a Bonus by using this expression in our client code:

 employee = employee + bonus; 

We will study the implementation of these types later in this section. For now, an operator overload that permits such an expression can be declared as follows:

     public static Employee operator+(Employee employee, Bonus bonus)     {       employee.Salary = employee.Salary + bonus.Amount;       return employee;     } 

An operator overload is declared like a parameterized method. This operator is a static method and has public visibility. These directives within the method declaration are really there to make the code look consistent with other type members because all operator overloads must be public and static. The whole point of operators is to create simple, intuitive client-code expressions; the operator overload must be public to allow the operator to be used in client code. We generalize operator overloading because it enables us to use the operator for all instances of a type. We don't want an operator to be available for selected instances of a type; therefore, the operator overload must also be static.

The next part of the declaration is the return type of the operator. If we consider adding two integers together with the + operator, the return value (the sum) is also of type integer. In this example we will be returning an Employee type.

Next follow the parameters – an Employee type and a Bonus type. Examples for these types will follow shortly. The number and the order of parameters is important when creating operator overload methods:

Unary Operators

Must be declared to accept a single parameter and return a result. For instance, the ++ operator must accept a single parameter, and return the parameter incremented by 1.

Binary Operators

Must accept two parameters, representing the left and right sides of the expression. A binary operator overload then returns an appropriate result. For instance the * operator must accept the left and right side arguments and return their product.

Comparison Operators

Accept two parameters representing the left and right sides of the expression and return a boolean result. For instance the > operator must return true if the left-hand side parameter is greater than the right-hand side parameter.

Our example is based on the + operator which is a binary operator. The + operator accepts two parameters representing the left and right sides of the expression. Here is the desired client code expression:

     employee = employee + bonus; 

and here is the method signature:

     (Employee employee, Bonus bonus) 

As you can see, the operator overload method accepts an Employee parameter first as the left-hand side of the expression. The second parameter is of type Bonus; this is the right-hand side of the expression.

The method contains all of the necessary workings for how the + operator should behave while adding an Employee type with a Bonus type. In this example, we are using the + operator to calculate the salary of an Employee by adding the Bonus.Amount property to the Employee.Salary property.

Similarly, like methods and properties, operators can also be defined for structures and classes. This is an important consideration because unlike classes, structures tend to be better candidates for operator overloading.

Operators and Classes

To demonstrate just how powerful operators can be, let's contrast an example that has an operator overload with an equivalent example that does not use operators.

Here is a simple example that does not use operator overloading. The code can be found in operators_no_operators.cs:

     using System;     class Bonus     {       private int bonus;       public int Amount       {         get { return bonus; }         set { bonus = value; }       }     }     class Employee     {       private string name;       private int salary;       public string Name       {         get { return name; }         set { name = value; }       }       public int Salary       {         get { return salary; }         set { salary = value; }       }     }     class CalculateBonus     {       static void Main()       {         Employee employee = new Employee();         Bonus bonus = new Bonus();         employee.Name = "James";         employee.Salary = 8000;         bonus.Amount = 1000;         Console.WriteLine(employee.Name + " - " + employee.Salary);         employee.Salary = employee.Salary + bonus.Amount;         Console.WriteLine(employee.Name + " - " + employee.Salary);       }     } 

When you run the program the following should be displayed in the console window:

     C:\Class Design\Ch 04>operators_no_operators.exe     James - 8000     James - 9000 

There is nothing special about this example. All the techniques used have already been covered in this chapter. We are calculating an employee's new salary by adding a bonus amount. This is done in the client code with the following expression:

     employee.Salary = employee.Salary + bonus.Amount; 

Although this style of programming works in the functional sense, it has two minor disadvantages:

  • It is not consistent with the way the rest of the .NET Framework works, and particularly when compared to primitive types. For example, to add two integers together we would not use an expression like this:

       numberone.Value = numberone.Value + numbertwo.Value 
  • The business logic to calculate the new employee salary is the expression. It could be wrapped inside a method to provide encapsulation, but a method does not provide an equivalent level of abstraction to that which many developers take for granted when adding two numbers together.

Operator overloading allows us to use the simple expressions normally associated with primitive data types but for our own types. For example, by overloading the + operator in the Employee type example, we could write code like this:

     employee = employee + bonus; 

To achieve this, we need to create an operator overload method within the Employee class. Change the Employee class as follows. The complete code can be found in operators_overload.cs:

     public static Employee operator+(Employee employee, Bonus bonus)     {       employee.Salary = employee.Salary + bonus.Amount;       return employee;     } 

Here we have overloaded the + operator by using an operator overload method. As you know, operator overloads are really just specialized methods. They can change the parameters and return their values. These are typical characteristics of parameterized methods.

To test the operator overload, change the client code within Calculate Bonus as follows:

      static void Main()      {        Employee employee = new Employee();        Bonus bonus = new Bonus();        employee.Name = "James";        employee.Salary = 8000;        bonus.Amount = 1000;        Console.WriteLine(employee.Name + " - " + employee.Salary);        employee = employee + bonus;        Console.WriteLine(employee.Name + " - " + employee.Salary);      } 

When you run the program the following should be displayed in the Console window –

    C:\Class Design\Ch 04>operators_overload.exe    James - 8000    James - 9000 

Here the client code is now able to use a simple expression to calculate the employee bonus. This expression is semantically identical to an expression to add two numbers or concatenate two strings.

We have now made the client code simpler by permitting a simple expression through a + operator overload. The operator overload actually performs the original complex expression that we would have had to use without the + operator overload. All we have done is move the more complex expression from the client code to the Employee class. This should be considered good design because the calculation of the new Employee Salary is encapsulated within Employee class.

This example makes sense because the expression Employee = Employee + Bonus; is intuitive.

Caution

It would not make sense, however, to overload an operator that permits an expression such as Employee = Employee + Employee; because adding two employees together does not make any sense.

Important

When considering operator overloading, it's best to think about what expressions make sense in the client code first. A good technique is to use pseudo code. Thinking about operator overloading in this way helps you create intuitive operator overloads that will make sense to the client code developers.

When used with forethought, operator overloading can lead to more intuitive programming with simpler client code expressions. But it is important to remember that the operator overload itself usually contains the expression that would have otherwise been used in the client code if the operator overload did not exist. It's worth considering how much effort you will save by creating operator overloads for your classes. If there are only one or two lines of client code that would otherwise use the more complex non-operator overloaded expression, it may not be worthwhile to go to the trouble of creating the operator overload in the first place.

Compiling Operators into MSIL

We will now examine the MSIL code emitted by the C# compiler for the operator overload. Run ildasm on the example operators_overload.exe. The following screen should be displayed after you have expanded the Bonus, CalculateBonus, and Employee nodes:

click to expand

The Employee + operator overload can be seen with the other Employee class members. We can also see that it is a method by double clicking on:

 op_Addition : class Employee(class Employee, class Bonus) 

The following screen should be displayed:

click to expand

Actually the C# compiler emits this method, and it looks and behaves like any other parameterized method. The method receives arguments of type Employee and Bonus. We can see the Employee get_Salary() method being invoked, following by the Bonus get_Amount() method. The method then performs the addition and finally calls the Employee set_Salary() method. Remember here that the get() and set() methods are the accessors of a property and that properties are specialized methods.

In a nutshell, operator overloads are represented as parameterized methods in MSIL code.

Operator Overload and Cross-Language Support

We mentioned earlier that operator overloading might be new to Visual Basic and Java developers switching to C#. Developers switching from C++ might already be familiar with operator overloading. C# supports operator overloading but Visual Basic .NET does not. Given that programs authored in Visual Basic .NET cannot create types with operator overloads, this difference between the two .NET languages raises one very big question – How does Visual Basic .NET client code call C# classes with operator overloads?

The following Visual Basic .NET client program illustrates how to use an operator overload authored in a C# class but consumed in Visual Basic .NET client code. The following example can be found in operator_overload.vb:

     //while compiling, use the /r: - reference switch for     //operators_overload.cs file. Remember to change the status of the     //Classes Employee and Bonus to public     Module CalculateBonus       Sub Main()         Dim emp As New Employee()         Dim bonus As New Bonus()         emp.Name = "James"         emp.Salary = 8000         bonus.Amount = 1000         emp = Employee.op_Addition(emp, bonus)         Console.WriteLine(emp.Name + " - " + emp.Salary.ToString())       End Sub     End Module 

Here we can see that Visual Basic .NET can still use a C# type with an operator overload, although in a non-intuitive way because we are invoking a method with an unfamiliar name. Also, this method is hidden from IntelliSense when accessing the C# class members. In Visual Basic .NET, we cannot use the operator overload expression in the same way as in a C# client program.

The operator overload is treated by Visual Basic .NET like all other parameterized methods; this ties in with the observations made when examining the MSIL code emitted by the C# compiler. Because Visual Basic .NET does not support the authoring of types with operator overloads, it doesn't support the customized expressions permissible in C# client code either.

If your C# classes are going to be consumed by non-C# client programs, it is a good idea to include a public method that exhibits the same behavior as the operator overload. This is better than invoking a hidden op_Addition() method (or other operator method) because a method intended for this purpose will be available in IntelliSense and can be named appropriately.

Symmetrical Operator Overloads

In the previous example we considered the following expression:

     employee = employee + bonus; 

This expression adds a Bonus type to an Employee type and returns an Employee type with the bonus added to the employee's salary. We created an operator overload for the + operator on the employee class to permit this client code expression.

However, we must also consider the following expression to be valid:

     employee = bonus + employee; 

By implication, these two expressions are the same and only differ by the order of the Employee and Bonus types. We would expect these two expressions to exhibit exactly the same behavior.

As operator overloads are really specialized methods, the order of the parameters passed to the method (which also equates to the order in which the expression should be used) is critical. The existing operator overload method will not support the alternative expression of :

     employee = bonus + employee; 

In these situations, we need to create a symmetrical operator overload, which is used to create a symmetrical expression when using a binary operator overload based on two different types. The symmetrical part comes from the requirement for an expression to be symmetrical; that is:

     employee = employee + bonus; 

is the same as:

     employee = bonus + employee; 
Important

When considering binary operations on the same type, symmetrical operator overloads are not required.

We can create a symmetrical operator overload by creating the first operator and overloading this with the new method to that performs the same calculation, but the parameters are the opposite way round. The details of method overloading are discussed in Chapter 3, but a method can be overloaded when the method signature differs. The following code (found in operators_symmetrical.cs), demonstrates how to overload operators to create symmetrical expressions. Add the following operator + overload method to the Employee class:

     public static Employee operator+(Bonus bonus, Employee employee)       {         employee.Salary = employee.Salary + bonus.Amount;         return employee;       }     } 

Now add the following to the CalculateBonus class:

         ...         employee = bonus + employee;         Console.WriteLine(employee.Name + " - " + employee.Salary);       } 

Type Comparison and Equality

When working with your own types, it is often necessary to compare two instances of the same type to see if they are the same. This is because the results of such a comparison do not necessarily depend on the values of the type members' variable assignments and comparisons differ in behavior between reference types such as classes and value types such as structures. The differences are exactly the same as when a reference type or a value type is passed as a method parameter. Passing arguments and using parameters is really the same as a variable assignment using the = operator.

Consider the following expression where employee_one and employee_two are of type Employee used in the earlier examples:

     employee_one = employee_two; 

Because the Employee type is a class and thus a reference type, the above assignment creates a shallow copy of the Employee object resulting in two references to the same instance. Comparisons between these two variables using either the == operator or Equals() method will always return true because they both point to the same instance. This is known as referential equality because the test for equality is based on the references stored in the two variables, which will always be the same for a reference type.

Comparisons between two separate instances of the Employee type will always return false even if they contain the same member data. This is because they do not have referential equality and the two variables point to different heap-based Employee instances. Again, no comparison made on the Employee type member data.

If we change the Employee type to a structure and therefore a value type, assignments create a deep copy of the structure thus creating two completely separate copies of the same structure. Changes made to one variable would not affect the other and comparisons will usually return false because the data in each of the structures is different.

We can see from the above rules that performing any kind of comparison will produce predictable results. If we have two references to the same object, referential equality works in our favor because both references point to the same heap-based object and the data will always be equal when a comparison is made between one reference and the other. When we have two separate instances of the same reference type we have to perform a member-by-member comparison to test for value equality.

Important

Overloading the == operator provides a predictable approach to comparing two separate instances of the same reference type by providing value equality.

To do this, we need to overload the == operator and the != operator to provide your own customized comparisons. The == operator should return true when the member comparison is the same, and != should return false if the member comparison is not the same. This is called testing for value equality. We can also compare two instances of a type using the Equals() method:

     If (employee_one.Equals(employee_two))     {       ...     } 

The Equals() method is a member of System.Object and is therefore implicitly available on all types, reference or value. It must also be overridden along with the == and != operator overloads to provide a complete and rounded comparison mechanism between two instances of the same reference type. In fact, the C# compiler forces you to override the Equals method when overloading the == and != operators.

From Chapter 2 of this book, you should also be familiar with GetHashCode() method. This method allows a type to work correctly in a hash table by providing a unique hash value of the object. The GetHashCode() method should also be overridden to exhibit a customized hash value of the type.

The following code can be found in operators_equality.cs:

     using System;     class Employee     {       private string name;       private int salary;       public string Name       {         get { return name; }         set { name = value; }       }       public int Salary       {         get { return salary; }         set { salary = value; }       }       public static bool operator==(Employee left_employee,                                     Employee right_employee)       {         if (left_employee.Name == right_employee.Name)           return true;         else           return false;       }       public static bool operator!=(Employee left_employee, Employee     right_employee)       {         if (left_employee.Name != right_employee.Name)           return true;         else           return false;       }       public override bool Equals(object type)       {         if (!(type is Employee))           return false;         else           return this ==(Employee)type;       }       public override int GetHashCode()       {         return this.Name.GetHashCode() + this.Salary.GetHashCode();       }     }       class CalculateBonus       {       static void Main()       {         Employee employee_one = new Employee();         Employee employee_two = new Employee();         Employee employee_three = new Employee();         employee_one.Name = "James";         employee_two.Name = "Steve";         employee_three = employee_one;         if (employee_one == employee_two)           Console.WriteLine("Employee One is the same as Employee Two");         else           Console.WriteLine("Employee One is NOT the same as Employee                              Two");         if (employee_one == employee_three)           Console.WriteLine("Employee One is the same as Employee                              Three");         else           Console.WriteLine("Employee One is NOT the same as Employee                              Three");         employee_three.Name = "Steve";         if (employee_two == employee_three)           Console.WriteLine("Employee Two is the same as Employee                              Three");         else           Console.WriteLine("Employee Two is NOT the same as Employee                              Three");       }     } 

Running the program will produce the following results:

     C:\Class Design\Ch 04>operators_equality.cs     Employee One is NOT the same as Employee Two     Employee One is the same as Employee Three     Employee Two is the same as Employee Three 

Let's discuss the main points of interest in this program:

  • The == operator has been overloaded to perform a value equality comparison (instead of referential equality) on the Employee Name property. Two separate instances of the Employee type are deemed to have value equality if their names are the same.

  • The != operator has been overloaded to return false if the Employee Name property comparison is not the same. Overloading the != operator is mandatory when overloading the == operator because the operators == and != work as a pair.

  • The Equals() method has been overridden to exhibit exactly the same behavior as the == operator. The Equals() method does not throw an exception if an invalid argument is passed. This is because if the argument is invalid, the comparison is always going to be false. The Equals() method therefore checks the type of the argument first before proceeding with the Employee type member comparison.

  • GetHashCode() has been overridden to return the combined hash code of the Employee Name and Salary properties.

  • Three new instances of the Employee class are created:

    • employee_one, Name property is assigned the value James.

    • employee_two, Name property is assigned the value Steve.

    • employee_three, Name property is left unassigned.

  • A comparison is made between employee_one and employee_two. This returns false, because they have different names.

  • employee_three is assigned to employee_one; employee_one and employee_three now hold separates references to the same Employee instance. The comparison returns true because they implicitly have the same Name property value.

  • The Name property of employee_three Name is assigned the value Steve, when compared with employee_two, which is a separate instance of the Employee type, the comparison returns true, even through these two instances do not have referential equality. The == operator overload has deemed them the same because their Name property values are the same.

In short we can note the following points about type comparison and equality:

  • Overloading the == and != operators can be used to test for value equality which overrides the default behavior of testing for referential equality. Normal behavior when comparing reference types is to check for referential equality and return true or false.

  • When overloading the == and != operators, a type member comparison should be made between two instances of the same type. This can override the default behavior of testing for referential equality and return true if some or all of the type members are the same.

  • In addition to overloading the == and != operators, you should also override the Equals() and GetHashCode() methods. The Equals() method is also used to test for equality and the GetHashCode() method is used to produce a hashed value of the type instance.

Operators and Structures

The examples we saw so far were based on a class representing an Employee. The benefits of providing operator overloads for classes similar to the Employee class are limited. This is because the number of operators we can overload is limited.

The golden rule we have established so far is – overload only those operators where the resulting client code expression ‘makes sense’. In the previous code illustrations that were based on an Employee class, we used these examples:

  • The expression employee = employee + bonus; makes sense because we can intuitively guess that the employee is being given a bonus.

  • The expression employee = bonus + employee; also makes sense because this is a symmetrical expression to the above.

  • The expression employee_one == employee_two makes sense because we know that this is a Boolean comparison between two instances of the same type.

  • Expressions such as employee = employee + employee; do not make any sense, because an employee cannot be added to another employee.

When we consider some of the other operators we could overload for the Employee class, we can also see that these operators make either little or no sense if applied to an Employee class:

  • Employee++

  • Employee * Employee

  • Employee >= Employee

  • Employee < Employee

When we look at the way in which operators are used on primitive types, it is fair to say that operators have a broader appeal and increased value with simple values types. For instance, using operators such as ++, *, <, >, or >= makes perfect sense with a number such as an integer.

Important

As programmers, we instinctively recognize operators and intuitively know what behavior to expect when they are used with primitive and string types.

Structures are ideal candidates for creating complex value types. If we think about the coordinates on a graph or an arithmetic fraction we can see that they are really more complex versions of existing primitive types. When representing entities like these in an application, value type structures (struct) are better choices than reference type classes because:

  • They are comparatively lightweight

  • They have a deterministic lifecycle and are not subject to garbage collection

We can see that structures are almost always better candidates for operator overloading than classes. To demonstrate the differences between structures and classes and the additional possibilities available for operator overloading, we will now create an application based on a fraction, which is a complex value type.

We have a number of operators that we can overload to create meaningful client code expressions with a fraction. To start with, we will create operators to handle subtractions and additions.

We can see these benefits in the following example, which can be found in operators_structs.cs:

     using System;     public struct Fraction     {       private int numerator;       private int denominator;       public int Numerator       {         get { return numerator; }         set { numerator = value; }       }       public int Denominator       {         get { return denominator; }         set { denominator = value; }       }       private static int lcd(int x, int y)       {         int r, t = x * y;         while (true)         {           r = x % y;           if (r == 0) break;           x = y; y = r;         }         return (t / y);       }       public static Fraction operator+(Fraction lh_fraction,         Fraction rh_fraction)       {         Fraction result = new Fraction();         result.Denominator = lcd(lh_fraction.Denominator,           rh_fraction.Denominator);         result.Numerator = lh_fraction.Numerator *           (result.Denominator / lh_fraction.Denominator) +           rh_fraction.Numerator *           (result.Denominator / rh_fraction.Denominator);         return result;       }       public static Fraction operator-(Fraction lh_fraction,         Fraction rh_fraction)       {         Fraction result = new Fraction();         result.Denominator = lcd(lh_fraction.Denominator,           rh_fraction.Denominator);         result.Numerator = lh_fraction.Numerator *           (result.Denominator / lh_fraction.Denominator)           rh_fraction.Numerator *           (result.Denominator / rh_fraction.Denominator);         return result;       }       public override string ToString()       { return (numerator.ToString() + "/" + denominator.ToString()); }     }     class FractionCalculator     {       static void Main()       {         Fraction fraction1 = new Fraction();         fraction1.Numerator = 2;         fraction1.Denominator = 5;         Fraction fraction2 = new Fraction();         fraction2.Numerator = 1;         fraction2.Denominator = 7;         Console.WriteLine("Fraction 1 = " + fraction1.ToString());         Console.WriteLine("Fraction 2 = " + fraction2.ToString());         Console.WriteLine();         Console.WriteLine("Fraction 1 + Fraction 2 = " + (fraction1 +                            fraction2).ToString());         Console.WriteLine("Fraction 1 - Fraction 2 = " + (fraction1                            fraction2).ToString());       }     } 

When the program is executed you should see the following results:

    C:\Class Design\Ch 04>operators_structs.exe    Fraction 1 = 2/5    Fraction 2 = 1/7    Fraction 1 + Fraction 2 = 19/35    Fraction 1 - Fraction 2 = 9/35 

Lets go through the points of interest within this example:

  • We have declared a Fraction type as a structure and not a class. It is therefore a value type.

  • Value types are better candidates for operator overloading. The + and the operators makes sense in the client code. We can also envisage other meaningful expressions using operators such as *,/,<, and >.

  • The Fraction type overrides the ToString() method. This is to create a human-readable string representation of the Fraction which helps in our example.

  • The Fraction structure includes a static method (lcd) to calculate the lowest common denominator using Euclid's algorithm.

Operator Overloading in Pairs

Some operators tend to be used in pairs. Consider the > and < operators. When overloading these operators, it makes sense to overload both operators and not just one of them. When working with types authored by somebody else, we would find it incredibly frustrating to find that only one half of a pair of operators had been overloaded for the type! Luckily, the C# compiler exercises some common sense and forces us to overload both of a pair of operators.

Extend the Fraction type by adding the code given below. This code is saved as operators_pairs.cs:

     public static bool operator>(Fraction lh_fraction, Fraction     rh_fraction)     {       int lowest;       lowest = lcd(lh_fraction.Denominator,         rh_fraction.Denominator);       if (lh_fraction.Numerator *         (lowest / lh_fraction.Denominator) >         rh_fraction.Numerator *         (lowest / rh_fraction.Denominator))         return true;       else         return false;     }     public static bool operator<(Fraction lh_fraction, Fraction     rh_fraction)     {       return !(lh_fraction > rh_fraction);     } 

Add the following lines to the client code in FractionCalculator:

        . . . . .         Console.WriteLine();         Console.WriteLine("Fraction 1 > Fraction 2? " + (fraction1 >     fraction2).ToString());         Console.WriteLine("Fraction 1 < Fraction 2? " + (fraction1 <     fraction2).ToString());         Console.WriteLine("Fraction 2 > Fraction 1? " + (fraction2 >     fraction1).ToString());         Console.WriteLine("Fraction 2 < Fraction 1? " + (fraction2 <     fraction1).ToString());       }     } 

The program should now produce the following output:

     C:\Class Design\Ch 04>operators_pairs     Fraction 1 = 2/5     Fraction 2 = 1/7     Fraction 1 + Fraction 2 = 19/35     Fraction 1 - Fraction 2 = 9/35     Fraction 1 > Fraction 2? True     Fraction 1 < Fraction 2? False     Fraction 2 > Fraction 1? False     Fraction 2 < Fraction 1? True 

The Fraction example is now almost complete. We can perform additions, subtractions, and make greater than and less than comparisons. The client code expressions make sense. We could also go a stage further and implement the == and != operators already discussed and we could also implement the >= and <= operators which are obviously similar to the > and < operators.

When overloading operators you should always make the full complement of operators available. For instance, if the > and < operators have been overloaded, the >= and <= operators should be implemented as well.

Operator Overload Best Practice

We saw some examples on where and how to use operator overloading both for classes and structures. Operator overloading can sometimes be used in the wrong way. Here is a list of situations where operator overloading should not be used:

  • Never create an operator overload when the resulting expression does not make sense. For example, type_x = type_y + type_z;.

  • Structures, which tend to be used to represent complex value types, are inherently better choices for operator overloading than classes, which tend to be used for complex business entities. Try to limit the use of operators on your own types to structures used to represent complex value type data.

  • Operators such as > and < ; == and !=; and >= and =< must be overloaded in pairs. The C# compiler will not let one half of a pair be overloaded on its own. When overloading in pairs consider implementing the comparison logic in the first operator then returning the opposite (NOT \ !) result of the first operator in the second operator.

  • Overloading the == and != operators can be used to test for value equality between two instances of the same type. This can be especially useful when comparing two reference types, which would normally be compared by referential equality. When overloading these operators, remember to override the Equals() and GetHashCode() methods.

  • When overloading binary operators such as + to add two different types, create an overload of the same operator but reverse the parameter sequence. This will permit symmetrical expressions in client code.

Operators Summary

Operators can be used to create simple meaningful expressions in the client code. These expressions should be intuitive to the client code developer and should make sense from the business purpose of the application. We also saw that operators are really just specialized methods, like scalar properties and indexers. The MSIL code emitted through the C# compiler confirms this.

Here are some final thoughts to remember when designing your own types with operator overloads:

  • Consider operator overloads from the client-code perspective first. It can help to think in pseudo-code terms first to create the desired instinctive expression. This technique helps design operator overloads that are meaningful and intuitive.

  • Operator overloads can be used to encapsulate expressions that are often used within an application. In fact, the operator overload usually contains the same expression that would have been used by the client code if the operator overload did not exist.

  • Only use an operator overload if there are tangible benefits when developing the client code, not if only one or two lines of client code could be rewritten with the simplified expression.

  • Because operator overloads are methods, they can be overloaded like any other method, provided we have different method signatures. Several overloads of the same operator for different types can make the overall client code more consistent. Overloading can also be used to create symmetrical expressions.




C# Class Design Handbook(c) Coding Effective Classes
C# Class Design Handbook: Coding Effective Classes
ISBN: 1590592573
EAN: 2147483647
Year: N/A
Pages: 90

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