Operator Overloading in C#

Operator overloading allows operators to be defined on a class or struct so it can be used with operator syntax. This is most useful on data types where there’s a good definition for what a specific operator means, thereby allowing an economy of expression for the user.

Chapter 29 covers overloading the relational operators (==, !=, >, <, >=, and <=).

Chapter 25 covers overloading conversion operators.

1. Unary Operators

All unary operators are defined as static functions that take a single operator of the class or struct type and return an operator of that type. You can overload the following operators:

+-!~++–true false

The first six unary overloaded operators are called when the corresponding operation is invoked on a type. The true and false operators are available for Boolean types where if (a == true) isn’t equivalent to the following:

if (! (a == false))

This happens in the SQL types in the System.Data.SQL namespace, which have a null state that’s neither true nor false. In this case, the compiler will use the overloaded true and false operators to correctly evaluate such statements. These operators must return type bool.

There’s no way to discriminate between the before and after increment or decrement operations. Because the operators are static (and therefore have no state), this distinction isn’t important.

2. Binary Operators

All binary operators take two parameters, at least one of which must be the class or struct type in which the operator is declared. A binary operator can return any type but will typically return the type of the class or struct in which it’s defined.

You can overload the following binary operators:

+-*/%&|^<<>>== != >= <= > <

3. An Example

The following class implements some of the overloadable operators:

using System; struct RomanNumeral

{

public RomanNumeral(int value) {

this.value = value;

}

public override string ToString()

{

return(value.ToString());

}

public static RomanNumeral operator -(RomanNumeral roman)

{

return(new RomanNumeral(-roman.value));

}

public static RomanNumeral operator +(

RomanNumeral        romanl,

RomanNumeral        roman2)

{

return(new RomanNumeral(

romanl.value + roman2.value));

}

public static RomanNumeral operator ++(

RomanNumeral roman)

{

return(new RomanNumeral(roman.value + l));

}

int value;

}

class Test {

public static void Main()

{

RomanNumeral        romanl = new RomanNumeral(l2);

RomanNumeral        roman2 = new RomanNumeral(l25);

Console.WriteLine(“Increment: {0}”, romanl++);

Console.WriteLine(“Addition: {0}”, romanl + roman2);

}

}

This example generates the following output:

Increment: 12

Addition: 138

4. Restrictions

It isn’t possible to overload member access, member invocation (function calling), or the =, &&, ||, ?:, or new operators. This is for the sake of simplicity; although you can do interesting things with such overloadings, it greatly increases the difficulty of understanding the code, since you have to always remember that member invocation (for example) could be doing something special.1 The new operator can’t be overloaded because the .NET runtime is responsible for managing memory, and in the C# idiom, new just means “give me a new instance of.”

It’s also not possible to overload the compound assignment operators (+=, *=, and so on), since they’re always expanded to the simple operation and an assignment. This avoids cases where one is defined and the other isn’t, or cases where (shudder) they’re defined with different meanings.

5. Guidelines

Operator overloading is a feature you should use only when necessary. In other words, use it only when it makes things easier for the user.

A good example of operator overloading is defining arithmetic operations on a complex number or matrix class.

A bad example is defining the increment (++) operator on a string class to mean “increment each character in the string.” A good guideline is that unless a typical user would understand what the operator does without any documentation, you shouldn’t define it as an operator. Don’t make up new meanings for operators.

In practice, the equality (==) and inequality (!=) operators are the ones you’ll define most often, since if you don’t do this, you may get unexpected results.

If the type behaves like a built-in data type, such as the BinaryNumeral class, it may make sense to overload more operators. At first look, it might seem that since the BinaryNumeral class is really just a fancy integer, it could just derive from the System.Int32 class and get the opera­tors for free.

This won’t work for a couple of reasons. First, you can’t use value types as base classes, and Int32 is a value type. Second, even if it’s possible, it won’t really work for BinaryNumeral, because a BinaryNumeral isn’t an integer; it supports only a small part of the possible integer range. Because of this, derivation isn’t a good design choice. The smaller range means that even if BinaryNumeral is derived from int, there isn’t an implicit conversion from int to BinaryNumeral, and any expressions therefore require casts.

Even if these facts weren’t true, however, it still wouldn’t make sense, since the whole point of having a data type is to have something that’s lightweight, and a struct would be a better choice than a class. Structs, of course, can’t derive from other objects.

6. A Complex Number Class

The following struct implements a complex number, with a few overloaded operators. Note that there are nonoverloaded versions for languages that don’t support overloaded operators.

using System;

struct Complex

{

float real; float imaginary;

public Complex(float real, float imaginary)

{

this.real = real;

this.imaginary = imaginary;

}

public float Real {

get

{

return(real);

}

set

{

real = value;

}

}

public float Imaginary {

get

{

return(imaginary);

}

set

{

imaginary = value;

}

}

public override string ToString()

{

return(String.Format(“({0}, {1}i)”, real, imaginary));

}

public static bool operator==(Complex c1, Complex c2)

{

if ((c1.real == c2.real) &&

(c1.imaginary == c2.imaginary))

return(true);

else

return(false);

}

public static bool operator!=(Complex c1, Complex c2)

{

return(!(c1 == c2));

}

public override bool Equals(object o2)

{

Complex c2 = (Complex) o2;

return(this == c2);

}

public override int GetHashCode()

{

return(real.GetHashCode() ^ imaginary.GetHashCode());

}

public static Complex operator+(Complex c1, Complex c2)

{

return(new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary));

}

public static Complex operator-(Complex c1, Complex c2)

{

return(new Complex(c1.real – c2.real, c1.imaginary – c2.imaginary));

}

// product of two complex numbers

public static Complex operator*(Complex c1, Complex c2)

{

return(new Complex(c1.real * c2.real – c1.imaginary * c2.imaginary,

c1.real * c2.imaginary + c2.real * c1.imaginary));

// quotient of two complex numbers public static Complex operator/(Complex cl, Complex c2)

{

if ((c2.real == 0.0f) &&

(c2.imaginary == 0.0f))

throw new DivideByZeroException(“Can’t divide by zero Complex number”);

float newReal =

(c1.real * c2.real + c1.imaginary * c2.imaginary) /

(c2.real * c2.real + c2.imaginary * c2.imaginary);

float newImaginary =

(c2.real * c1.imaginary – c1.real * c2.imaginary) /

(c2.real * c2.real + c2.imaginary * c2.imaginary);

return(new Complex(newReal, newImaginary));

}

// non-operator versions for other languages…

public static Complex Add(Complex c1, Complex c2)

{

return(c1 + c2);

}

public static Complex Subtract(Complex c1, Complex c2)

{

return(c1 – c2);

}

public static Complex Multiply(Complex c1, Complex c2)

{

return(c1 * c2);

}

public static Complex Divide(Complex c1, Complex c2)

{

return(c1 / c2);

}

}

class Test {

public static void Main()

{

Complex c1 = new Complex(3, 1);

Complex c2 = new Complex(1, 2);

Console.WriteLine(“c1 == c2: {0}”, c1 == c2);

Console.WriteLine(“c1 != c2: {0}”, cl ! = c2);

Console.WriteLine(“c1 + c2 = {0}”, c1 + c2);

Console.WriteLine(“c1 – c2 = {0}”, c1  –   c2);

Console.WriteLine(“c1 * c2 = {0}”, c1 *   c2);

Console.WriteLine(“c1 / c2 = {0}”, c1  /   c2);

}

}

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *