Operator Overloading


The last section looked at overriding Equals() and provided the guideline that the class should also implement == and !=. The term for implementing any operator is operator overloading, and this section describes how to do this, not only for == and !=, but also for other supported operators.

For example, string provides a + operator that concatenates two strings. This is perhaps not surprising, because string is a predefined type, so it could possibly have special compiler support. However, C# provides for adding + operator support to any type. Virtually all operators are supported, except for assignment operators; there is no way to change the behavior of the = operator.

Comparison Operators (==, !=, <, >, <=, >=)

Once Equals() is overridden, there is a possible inconsistency. Two objects could return true for Equals() but false for the == operator because == performs a reference equality check by default as well. To correct this it is important to overload the equals (==) and not equals (!=) operators as well.

For the most part, the implementation for these operators can delegate the logic to Equals(), or vice versa. However, some initial null checks are required first (see Listing 9.6).

Listing 9.6. Implementing the == and != Operators

public sealed class Coordinate {  // ...  public static bool operator ==(      Coordinate leftHandSide,      Coordinate rightHandSide)  {  // Check if leftHandSide is null.  // (operator== would be recursive)  if (ReferenceEquals(leftHandSide, null))  {      // Return true if rightHandSide is also null      // but false otherwise.      return ReferenceEquals(rightHandSide, null);  }  return (leftHandSide.Equals(rightHandSide));  }  public static bool operator !=(      Coordinate leftHandSide,      Coordinate rightHandSide)  {      return !(leftHandSide == rightHandSide);  } }

Note that to perform the null checks, you cannot use an equality check for null (leftHandSide == null). Doing so would recursively call back into the method, resulting in a loop until overflowing the stack. To avoid this you call ReferenceEquals() to check for null.

Binary Operators (+, -, *, /, %, &,|, ^, <<,>>)

You can add an Arc to a Coordinate. However, the code so far provides no support for the addition operator. Instead, you need to define such a method, as Listing 9.7 shows.

Listing 9.7. Adding an Operator

struct Arc {  public Arc(      Longitude longitudeDifference,      Latitude latitudeDifference)  {      LongitudeDifference = longitudeDifference;      LatitudeDifference = latitudeDifference;  }  public readonly Longitude LongitudeDifference;  public readonly Latitude LatitudeDifference; } struct Coordinate {  // ...  public static Coordinate operator +(Coordinate source, Arc arc)  {  Coordinate result = new Coordinate(new Longitude(source.Longitude + arc.LongitudeDifference),  new Latitude(source.Latitude + arc.LatitudeDifference));  return result;  } }

The +, -, *, /, %, &,|, ^, <<, and >> operators are implemented as binary static methods where at least one parameter is of the containing type. The method name is the operator prefixed by the word operator as a keyword. As shown in Listing 9.8, given the definition of the - and + binary operators, you can add and subtract an Arc to and from the coordinate.

Listing 9.8. Calling the and + Binary Operators

public class Program {  public static void Main()  {      Coordinate coordinate1,coordinate2;      coordinate1 = new Coordinate(new Longitude(48, 52), new Latitude(-2, -20));      Arc arc = new Arc(new Longitude(3), new Latitude(1));      coordinate2 = coordinate1 + arc;      coordinate2 = coordinate2 - arc;      coordinate2 += arc;  } }

The results of Listing 9.8 appear in Output 9.3.

Output 9.3.

51° 52' 0 E -1° -20' 0 S 51° 52' 0 E -1° -20' 0 S 54° 52' 0 E 0° -20' 0 S

The and + operators on Coordinate return a third coordinate after subtracting Arc. This allows you to string multiple operators and operands together, as in result = coordinate1 + coordinate2 + coordinate3 coordinate4;.

This works because the result of the first operand (coordinate1 + coordinate2) is another Coordinate, which you can then add to the next operand.

In contrast, consider if you provided a operator that had two Coordinates as parameters and returned a double corresponding to the distance between the two coordinates. Adding a double to a Coordinate is undefined and, therefore, you could not string operators and operands. Caution is in order when defining operators that behave this way, because doing so is counterintuitive.

Combining Assignment with Binary Operators (+=, -=,*=, /=, %=, &=...)

As previously mentioned, there is no support for overloading the assignment operator. However, assignment operators in combination with binary operators (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, and >>=) are effectively overloaded when overloading the binary operator. Given the definition of a binary operator without the assignment, C# automatically allows for assignment in combination with the operator. Using the definition of Coordinate in Listing 9.7, therefore, you can have code such as:

coordinate += arc;


which is equivalent to the following:

coordinate = coordinate + arc;


Conditional Logical Operators (&&, ||)

Like assignment operators, conditional logical operators cannot be overloaded explicitly. However, since the logical operators & and | can be overloaded, and the conditional operators comprise the logical operators, effectively it is possible to overload conditional operators. x && y is processed as x & y, where y must evaluate to true. Similarly, x || y is processed as x | y only if x is false. To enable support for evaluating a type to TRue or falsein an if statement, for exampleit is necessary to override the TRue/false unary operators.

Unary Operators (+, -, !, ~, ++, --, true, false)

Overloading unary operators is very similar to overloading binary operators, except that they take only one parameter, also of the containing type. Listing 9.9 overloads the + and operators for Longitude and Latitude and then uses these operators when overloading the same operators in Arc.

Listing 9.9. Overloading the and + Unary Operators

 public struct Latitude {    // ...    public static Latitude operator -(Latitude latitude)              {                                                              return new Latitude(-latitude.DecimalDegrees);               }                                                           public static Latitude operator +(Latitude latitude)               {                                                                 return latitude;                        }                                                      } _______________________________________________________________________________ _______________________________________________________________________________ public struct Longitude {    // ...    public static Longitude operator -(Longitude longitude)                          {                                                                               return new Longitude(-longitude.DecimalDegrees);                              }                                                                       public static Longitude operator +(Longitude longitude)                         {                                                                             return longitude;                }                                                                   } ___________________________________________________________________________ ___________________________________________________________________________ public struct Arc {   // ...   public static Arc operator -(Arc arc)   {       // Uses unary  operator defined on       // Longitude and Latitude      return new Arc(- arc.LongitudeDifference,               -arc.LatitudeDifference);   }   public static Arc operator +(Arc arc)   {      return arc;   } }

Just as with numeric types, the + operator in this listing doesn't have any effect and is provided for symmetry.

Overloading true and false has the additional requirement that they both be overloaded. The signatures are the same as other operator overloads; however, the return must be a bool, as demonstrated in Listing 9.10.

Listing 9.10. Overloading the true and false Operators

public static bool operator false(IsValid item) {      // ... } public static bool operator true(IsValid item) {      // ... }

You can use types with overloaded true and false operators in if, do, while, and for controlling expressions.

Conversion Operators

Currently, there is no support in Longitude, Latitude, and Coordinate for casting to an alternate type. For example, there is no way to cast a double into a Longitude or Latitude instance. Similarly, there is no support for assigning a Coordinate using a string. Fortunately, C# provides for the definition of methods specifically to handle the casting of one type to another. Furthermore, the method declaration allows for specifying whether the cast is implicit or explicit.

Advanced Topic: Cast Operator (())

Implementing the explicit and implicit conversion operators is not technically overloading the cast operator (()). However, this is effectively what takes place, so defining a cast operator is common terminology for implementing explicit or implicit conversion.


Defining a conversion operator is similar in style to defining any other operator, except that the "operator" is the data type of the conversion. Additionally, the operator keyword follows a keyword that indicates whether the conversion is implicit or explicit (see Listing 9.11).

Listing 9.11. Providing an Implicit Conversion between Latitude and double

public struct Latitude {   // ...   public double DecimalDegrees   {      get { return _DecimalDegrees; }      set      {           _DecimalDegrees = Normalize(value);      }   }   // ...   public static implicit operator double(Latitude latitude)   {      return latitude.DecimalDegrees;   }   public static implicit operator Latitude(double degrees)   {      return new Latitude(degrees);   } }

With these conversion operators, you now can cast doubles implicitly to and from Latitude objects. Assuming similar conversions exist for Longitude, you can simplify the creation of a Coordinate object by specifying the decimal degrees portion of each coordinate portion (for example, coordinate = new Coordinate(43, 172);).

Guidelines for Conversion Operators

The difference between defining an implicit and an explicit conversion operator centers on preventing an unintentional implicit cast that results in undesirable behavior. You should be aware of two possible consequences of using the explicit conversion operator. First, conversion operators that throw exceptions should always be explicit. For example, it is highly likely that a string will not conform to the appropriate format that a conversion from string to Coordinate requires. Given the chance of a failed conversion, you should define the particular conversion operator as explicit, thereby requiring that you be intentional about the conversion and that you ensure that the format is correct, or that you provide code to handle the possible exception. Frequently, the pattern for conversion is that one direction (string to Coordinate) is explicit and the reverse (Coordinate to string) is implicit.

Note

Either the return or the parameter must be of the enclosing typein support of encapsulation. C# does not allow you to specify conversions outside the scope of the converted type.


A second consideration is the fact that some conversions will be lossy. Converting from a float (4.2) to an int is entirely valid, assuming an awareness of the fact that the decimal portion of the float will be lost. Any conversions that will lose data and not successfully convert back to the original type should be defined as explicit.

Referencing Other Assemblies

Instead of placing all code into one monolithic binary file, C# and the underlying CLI platform allow you to spread code across multiple assemblies. This enables you to reuse assemblies across multiple executables.

Beginner Topic: Class Libraries

The HelloWorld.exe program is one of the most trivial programs you can write. Real-world programs are more complex, and as complexity increases, it helps to organize the complexity by breaking programs into multiple parts. To do this, developers move portions of a program into separate compiled units called class libraries or, simply, libraries. Programs then reference and rely on class libraries to provide parts of their functionality. The power of this concept is that two programs can rely on the same class library, thereby sharing the functionality of that class library across the two programs and reducing the total amount of code needed.

In other words, it is possible to write features once, place them into a class library, and allow multiple programs to include those features by referencing the same class library. Later on, when developers fix a bug or add functionality to the class library, all the programs will have access to the increased functionality, just because they continue to reference the now improved class library.


To reuse the code within a different assembly, it is necessary to reference the assembly when running the C# compiler. Generally, the referenced assembly is a class library, and creating a class library requires a different assembly target from the default console executable targets you created thus far.

Changing the Assembly Target

The compiler allows you to create four different assembly types via the /target option.

  • Console executable: This is the default type of assembly, and all compilation thus far has been to a console executable. (Leaving off the /target option or specifying /target:exe creates a console executable.)

  • Class library: Classes that are shared across multiple executables are generally defined in a class library (/target:library).

  • Windows executable: Windows executables are designed to run in the Microsoft Windows family of operating systems and outside of the command console (/target:winexe).

  • Module: In order to facilitate multiple languages within the same assembly, code can be compiled to a module and multiple modules can be combined to form an assembly (/target:module).

Assemblies to be shared across multiple applications are generally compiled as class libraries. Consider, for example, a library dedicated to functionality around longitude and latitude coordinates. To compile the Coordinate, Longitude, and Latitude classes into their own library, you use the command line shown in Output 9.4.

Output 9.4.

>csc /target:library /out:Coordinates.dll Coordinate.cs IAngle.cs Latitude.cs Longitude.cs Arc.cs Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42 for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727 Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

Assuming you use .NET and the C# compiler is in the path, this builds an assembly library called Coordinates.dll.

Encapsulation of Types

Just as classes serve as an encapsulation boundary for behavior and data, assemblies provide a similar boundary among groups of types. Developers can break a system into assemblies and then share those assemblies with multiple applications or integrate them with assemblies provided by third parties.

By default, a class without any access modifier is defined as internal. The result is that the class is inaccessible from outside the assembly. Even though another assembly references the assembly containing the class, all internal classes within the referenced assemblies will be inaccessible.

Just as private and protected provide levels of encapsulation to members within a class, C# supports the use of access modifiers at the class level for control over the encapsulation of the classes within an assembly. The access modifiers available are public and internal, and in order to expose a class outside of the assembly, the class must be marked as public. Therefore, before compiling the Coordinates.dll assembly, it is necessary to modify the type declarations as public (see Listing 9.12).

Listing 9.12. Making Classes Available Outside an Assembly

public struct Coordinate {   // ... } public struct Latitude {   // ... } ________________________________________________________________________________ ________________________________________________________________________________ public struct Longitude {   // ... } ________________________________________________________________________________ ________________________________________________________________________________ public struct Arc {   // ... }

Similarly, declarations such as class and enum can also be either public or internal.

Advanced Topic: Additional Class Access Modifiers

You can decorate nested classes with any access modifier available to other class members (private, for example). However, outside of the class scope, the only available access modifiers are public and internal.


The internal access modifier is not limited to type declarations. It is also available on type members. Therefore, you can designate a type as public but mark specific methods within the type as internal so that the members are available only from within the assembly. It is not possible for the members to have a greater accessibility than the type. If the class is declared as internal, then public members on the type will be accessible only from within the assembly.

protected internalis another type member access modifier. Members with an accessibility modifier of protected internal will be accessible from all locations within the containing assembly and from classes that derive from the type, even if the derived class is not in the same assembly.

Beginner Topic: Type Member Accessibility Modifiers

The full list of access modifiers appears in Table 9.1.


Table 9.1. Accessibility Modifiers

Modifier

Description

public

Declares that the member is accessible up to whatever level the type is accessible. If the class is internal, the member will be internal. Public members will be accessible from outside the assembly if the containing type is public.

internal

Accessible from within the assembly only.

private

Accessible from within the containing type, but inaccessible otherwise.

protected

Declares the member as accessible within the containing type and any subtypes derived from it, regardless of assembly.

protected internal

Accessible from anywhere within the containing assembly and from any types derived from the containing type, even if the derived types are within a different assembly.


Referencing an Assembly

To access code within a different assembly, the C# compiler allows the developer to reference the assembly on the command line. The option is / reference (/r is the abbreviation), followed by the list of references. The Program class listing from Listing 9.8 uses the Coordinate class, and if you place this into a separate executable, you reference Coordinates.dll using the .NET command line shown in Output 9.5.

Output 9.5.

csc.exe /R:Coordinates.dll Program.cs

The Mono command line appears in Output 9.6.

Output 9.6.

msc.exe /R:Coordinates.dll Program.cs




Essential C# 2.0
Essential C# 2.0
ISBN: 0321150775
EAN: 2147483647
Year: 2007
Pages: 185

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