Conversion Operators


Mathematical and Logical Operators

Mathematical and logical operators can be unary or binary operators, and most binary and unary operators can be overloaded in a user-defined type. The sole ternary operator, which is the conditional operator, cannot be overloaded. This is the complete description of mathematical and logical operators that cannot be overloaded in a user-defined type:

  • Dot operator (identifier.member)

  • Call operator (methodname())

  • Assignment operators (=, +=, /=, %=, and so on)

  • Conditional operators (&&, ||, and ?:)

  • Check and unchecked operators

  • new operator

  • typeof operator

  • as and is operators

  • Array operator ([])

  • Pointer operators

The compound assignment operators, such as += and /=, are implicitly overloaded using the individual operators. For example, the += and /= operators are implicitly overloaded by overloading the + and / operators.

In addition, developers cannot use operator overloading to define new operators. For example, you could not define a ^^ operator, which does not otherwise exist.

There are some notable differences between usage in the C++ and C# languages. In C++, you can overload the assignment, new, and array operators. This is not allowed in C#. This may be an issue when porting code from C++ to C#. Instead of overloading the assignment operator, implement the ICloneable interface. The garbage collector (GC) is responsible for managing dynamic memory. For that reason, the new operator cannot be overloaded in managed code.

The array operator is commonly overloaded in C++ to create a secure array, where fence post errors are usually checked. Fencepost errors are automatically detected by the CLR, which eliminates one of the primary reasons to overload the array operator.

Overloading an operator redefines the behavior of that operator in an expression. However, you cannot change the syntax, precedence, or associativity of an operator with operator overloading. Overloading an operator does not change the core principles of using the operator. Assume that the division operator is overloaded in a user-defined type. The syntax for employing the division operator remains obj1/obj2. Precedence is also preserved. In obj1+obj2/obj3-obj4, the division operation is performed before a plus or minus operator, but after the increment or decrement operator. Multiple division operations, as in obj1/obj2+obj3/obj4 are evaluated from left to right, which maintains the normal associativity.

Implementation

Overloaded operators are implemented as static and public functions. Other modifiers, such as virtual and sealed, are not allowed. The unary operator has a single operand, whereas the binary operator has two operands. Parameters of an overloaded operator cannot be ref or out.

Here is the syntax for overloading a unary or binary operator method:

  • public static type operator unary(classtype operand)

  • public static type operator binary(type lhsoperand, type rhsoperand)

For a unary operator method, the operand is the same type as the containing class. For a binary operator method, at least one of the parameters must be the containing type. This allows the type to appear as lhs, rhs, or both lhs and rhs in the expression. In the obj+5 expression, the object instance is passed as the lhs operand to the operator+ method. The object instance is passed as the rhs operand in the 5+obj expression. The return type is any value type but not void.

The following is an example of the implementation and use of an overloaded operator. The ZClass is a simple class and wrapper for two integer fields. The operator+ method is overloaded to add two ZClass instances. Because the operator+ method is part of the class, it has access to the private members of that class. This is convenient when accessing parameters of that type within the operator method. In this code, the operator+ is called implicitly in the expression: obj1+obj2. You cannot call an operator function explicitly.

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             ZClass obj1=new ZClass(5,10);             ZClass obj2=new ZClass(15,20);             ZClass obj3=obj1+obj2;             Console.WriteLine(obj3.Total);         }     }     class ZClass {         public ZClass(int _fielda, int _fieldb) {             fielda=_fielda;             fieldb=_fieldb;         }         public static ZClass operator+(ZClass lhs, ZClass rhs) {             return new ZClass(lhs.fielda+rhs.fielda,                 lhs.fieldb+rhs.fieldb);         }         public int Total {             get {                 return fielda+fieldb;             }         }         protected int fielda, fieldb;     } } 

Operator methods are available in a derived class. At least one operand of the operator function remains the base type. However, you can freely substitute the derived instance with the base type operand and access the base type members. The return type can also be a base type, which might pose problems. As a return, assigning a base instance to a derived type causes a run-time error. In addition, the derived type can replace operator methods of the base class. Simply implement the operator function again in the derived class. The implementation in the derived class overloads the base operator method.

In the following code, YClass derives from the ZClass class and inherits operator+, fielda, and fieldb. (ZClass.operator+ was shown in the previous code.) YClass also implements an operator-function. In Main, both the operator+ and operator-functions are used. The operator+ on the YClass returns a ZClass instance, which is the base type. You cannot explicitly cast this result to a YClass without raising a run-time exception.

 namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             YClass obj1=new YClass(5,10);             YClass obj2=new YClass(15,20);             ZClass obj3=obj1+obj2;             Console.WriteLine(obj3.Total);             YClass obj4=obj1-obj2;             Console.WriteLine(obj4.Total);         }     }         // Partial listing         public class YClass:ZClass {             public YClass(int _fielda, int _fieldb)                 :base(_fielda, _fieldb) {             }             public static YClass operator-(YClass lhs, YClass rhs) {                 return new YClass(lhs.fielda-rhs.fielda,                    lhs.fieldb-rhs.fieldb);             }         }     } 

You can overload an operator member function. The general rules of overloading apply, and each overloaded operator member function must have a unique signature. Implicit conversion of operands to other types can limit the necessity for some operator overloading. Operators are typically overloaded to use the containing type as an lhs or rhs.

In the following code, the operator+ is overloaded three times:

 public static ZClass operator+(ZClass lhs, ZClass rhs) {     return new ZClass(lhs.fielda+rhs.fielda,         lhs.fieldb+rhs.fieldb); } public static ZClass operator+(ZClass lhs, int rhs) {     return new ZClass(lhs.fielda+rhs,         lhs.fieldb+rhs); } public static ZClass operator+(int lhs, ZClass rhs) {     return new ZClass(lhs+rhs.fielda,         lhs+rhs.fieldb); } 

All three operator member functions are used in the following code:

 obj3=obj1+obj2; obj1=obj1+10; obj2=20+obj2; 

Operator overloading is not stipulated in the Common Language Specification (CLS) and therefore not guaranteed in all managed languages. It is good practice to implement a parallel named method for each operator member function. The named method should call the operator member function as demonstrated in the following code:

 public class ZClass {     public ZClass(int _fielda, int _fieldb) {         fielda=_fielda;         fieldb=_fieldb;     }     public static ZClass operator+(ZClass lhs, ZClass rhs) {         return new ZClass(lhs.fielda+rhs.fielda,             lhs.fieldb+rhs.fieldb);     }     public ZClass Add(ZClass rhs) {         return this+rhs;     }     public int Total {         get {             return fielda+fieldb;         }     }     protected int fielda, fieldb; } 

The semantics of overloading certain mathematical and logical operators are unique. Special rules must be followed when overloading these operators in a type.

Increment and Decrement Operators

The Increment (++) and Decrement (--) operators have special semantics. These are unary operators, where the operand and return type must be the containing class or a derivative. To maintain the underlying behavior, the Increment and Decrement operator should return a modified instance of the current object, which is the operand. You can (but should not) return an entirely new instance. Remember, overloaded operators should preserve the underlying meaning of the operator. Finally, overloading the Increment or Decrement operator revises both the prefix and postfix usage of the operator.

Here is sample code showing an overloading of both the Increment and Decrement operators:

 public class ZClass {     public ZClass(int _fielda, int _fieldb) {         fielda=_fielda;         fieldb=_fieldb;     }     public static ZClass operator++(ZClass curr) {         ++curr.fielda;         ++curr.fieldb;         return curr;     } public static ZClass operator--(ZClass curr) {     --curr.fielda;         --curr.fieldb;         return curr;     }     public int Total {         get {             return fielda+fieldb;          }     }     public int fielda, fieldb; } 

LeftShift and RightShift Operators

The LeftShift and RightShift operators normally perform a binary shift. Both are binary operators. When overloading these operators, the first operand must be the same type as the containing class. The second operand must be an int, which is the amount of the shift. The return type can be anything except void. The LeftShift and RightShift operators are not paired operator methods. You can implement them independently of each other. However, I would recommend implementing both because the methods are logically related:

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             ZClass obj1=new ZClass(5,10);             ZClass obj2=obj1<<2;             Console.WriteLine(obj2.Total);         }     }     public class ZClass {         public ZClass(int _fielda, int _fieldb) {             fielda=_fielda;             fieldb=_fieldb;         }         public static ZClass operator<<(ZClass curr, int shift) {             int newa=curr.fielda<<shift;             int newb=curr.fieldb<<shift;             return new ZClass(newa, newb);         }         public static ZClass operator>>(ZClass curr, int shift) {             int newa=curr.fielda>>shift;             int newb=curr.fieldb>>shift;             return new ZClass(newa, newb);         }         public int Total {             get {                 return fielda+fieldb;             }         }         public int fielda, fieldb;     } } 

Operator True and Operator False

True and false are logical operators and are used in conditional expressions and sometimes in assignments. They are paired operator methods, which require both functions of the pair to be implemented. If either the operator true or operator false method is overloaded, both must be implemented. Both operators are unary operators, in which the current object is the operand. The operand must be the containing class type or a derivative, and the return type should be bool. Add operator true and operator false methods to classes that have a false or true representation. This representation is typically interpreted from the state of the object. When an object is used as a Boolean expression, the operator true method is called. The operator false method is called in other circumstances, such as an && expression. Operator overloading and the && operator are discussed later in this appendix.

This is sample code of an operator true and operator false. The operator true is used in the Main method at the if statement.

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             ZClass obj1=new ZClass(5,5);             if(obj1) {                 Console.WriteLine("obj1 is true");             }             else {                 Console.WriteLine("obj1 is false");             }         }     }     public class ZClass {         public ZClass(int _fielda, int _fieldb) {             fielda=_fielda;             fieldb=_fieldb;         }         public static bool operator true(ZClass curr) {             Console.WriteLine("ZClass.operator true");             return curr.fielda==curr.fieldb;         }         public static bool operator false(ZClass curr) {             Console.WriteLine("ZClass.operator false");             return curr.fielda!=curr.fieldb;         }         public int Total {             get {                 return fielda+fieldb;             }         }         protected int fielda, fieldb;     } } 

Paired Operators

In addition to operator true and operator false, the relational operators are paired operators. Paired operators enforce a practical guideline—which is that related operators should be overloaded to maintain consistency. For example, because they are logically related, you should overload both the operator== and operator!= methods. This is the complete list of paired relational operators, which are discussed in the following sections:

  • operator== and operator!=

  • operator<= and operator>=

  • operator< and operator>

  • operator| and operator&

Operator== and Operator!=

The overloaded operator== method should delegate to the Equals method, which ensures that the operator== and the Equals method exhibit consistent behavior. If not, the definition of equality for an instance will vary based on circumstances, which would be minimally confusing. The operator== and operator!= methods are paired methods. When overloading these operators, you must also implement the Equals method in the same class. The Equals method is inherited from System.Object:

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             ZClass obj1=new ZClass(5,10);             ZClass obj2=new ZClass(5,10);             ZClass obj3=new ZClass(5,15);             if(obj1==obj2) {                 Console.WriteLine("obj1 and obj2 are equal.");             }             else {                 Console.WriteLine("obj1 and obj2 are not equal.");             }             if(obj1==obj3) {                 Console.WriteLine("obj1 and obj3 are equal.");             }             else {                 Console.WriteLine("obj1 and obj3 are not equal.");             }         }     }     public class ZClass {         public ZClass(int _fielda, int _fieldb) {             fielda=_fielda;             fieldb=_fieldb;         }         public static ZClass operator+(ZClass lhs, ZClass rhs) {             return new ZClass(lhs.fielda+rhs.fielda,                 lhs.fieldb+rhs.fieldb);         }         public static bool operator==(ZClass lhs, ZClass rhs) {             return lhs.Equals(rhs);         }         public static bool operator!=(ZClass lhs, ZClass rhs) {             return !lhs.Equals(rhs);         }         public override bool Equals(object o) {             return this.GetHashCode()==o.GetHashCode();         }         public override int GetHashCode(){             return fielda+fieldb;         }         public int Total {             get {                 return fielda+fieldb;             }         }         protected int fielda, fieldb;     } } 

Operator|| and Operator&&

Actually, operator|| and operator&& cannot be overloaded directly. These are the logical and and or operators, respectively. Instead, operator|, operator&, operator true, and operator false combine to overload operator|| and operator&&. The bitwise operators are normally operator| and operator&. If a user-defined type is used in an && or || expression, the four mentioned operator functions must be overloaded in that type.

Because of short-circuiting, an && or || expression is sometimes partially evaluated. If the lhs of an && expression is false, the entire expression is false. This means the rhs can be ignored. For || expressions, the entire expression is true if the lhs is true. When that occurs, the rhs of the || expression can be ignored. Potential short-circuiting complicates the overloading of the && and || operators. Several steps are required to evaluate the && or || operators for a user-defined type.

Following are the steps for evaluating the && operator, where the expression is lhs && rhs. (Let us assume that lhs and rhs are the same type as the containing class.)

  1. The overloaded operator false method is called. If the method returns true, the expression short-circuits because the first part of the && expression is confirmed to be false.

  2. If short-circuiting, the expression evaluates to type.operator true(lhs).

  3. If the expression does not short-circuit, the overloaded operator& is called: result=type.operator&(lhs, rhs).

  4. Finally, type.operator true(result) is called. This method returns the final result of the lhs && rhs expression when short-circuiting does not occur.

Following are the steps for evaluating the || operator, where the expression is lhs || rhs:

  1. The overloaded operator true method is called. If the method returns true, the expression short-circuits. The expression short-circuits because the first part of the || expression is confirmed to be true.

  2. If short-circuiting, the expression evaluates to type.operator true(lhs).

  3. If the expression does not short-circuit, the overloaded operator | is called: result=type.operator|(lhs, rhs).

  4. Finally, type.operator true(result) is called. This method returns the final result of the lhs || rhs expression when short-circuiting does not occur.

For the operator& and operator| methods, the operand and return must be the same type.

In the following code, ZClass is overloaded to support the && and || expression. All four required methods are implemented: operator true, operator false, operator&, and operator|. The overloaded methods of the ZClass type display messages for auditing program flow. In Main, ZClass instances are used in a || expression. Because the operator true is hard-coded to return false, the expression will never short-circuit.

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             ZClass obj1=new ZClass(5,10);             obj1.name="obj1";             ZClass obj2=new ZClass(15,20);             obj2.name="obj2";             if(obj1 || obj2) {                 Console.WriteLine("expression evaluated to true");             }         }         class ZClass {             public ZClass(int _fielda, int _fieldb) {                 fielda=_fielda;                 fieldb=_fieldb;             }             public static ZClass operator &(ZClass obj1,ZClass obj2) {                 Console.WriteLine("operator &");                 ZClass result= new ZClass(obj1.fielda&obj2.fielda,                                         obj1.fieldb&obj2.fieldb);                 result.name="result";                 return result;             }             public static ZClass operator |(ZClass obj1,ZClass obj2) {                 Console.WriteLine("operator |");                 ZClass result= new ZClass(obj1.fielda|obj2.fielda,                                         obj1.fieldb|obj2.fieldb);                 result.name="result";                 return result;             }             public static bool operator true(ZClass obj) {                 Console.WriteLine("operator true({0})",obj.name);                 return false;             }             public static bool operator false(ZClass obj) {                 Console.WriteLine("operator false({0})",obj.name);                 return true;             }             public int Total {                 get {                     return fielda+fieldb;                 }             }             public string name;             protected int fielda, fieldb;         }     } } 




Programming Microsoft Visual C# 2005(c) The Language
Microsoft Visual Basic 2005 BASICS
ISBN: 0619267208
EAN: 2147483647
Year: 2007
Pages: 161

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