Operators can be overloaded to provide more natural syntax for custom types. Operator overloading is most appropriately used for implementing custom structs that represent fairly primitive data types. For example, a custom numeric type is an excellent candidate for operator overloading.
The following symbolic operators can be overloaded:
+ - * / ++ -- ! ~ % & | ^ == != < << >> >
Implicit and explicit conversions can also be overridden (with the
implicit
and explicit
keywords), as can the literals true
and false
, and the unary +
and −
operators.
The compound assignment operators (e.g., +=
, /=
) are
automatically overridden when you override the noncompound operators
(e.g., +
, /
).
An operator is overloaded by declaring an operator function. An operator function must be static, and at least one of the operands must be the type in which the operator function is declared.
In the following example, we define a struct called Note
, representing a musical note, and then
overload the +
operator:
public struct Note
{
int value;
public Note (int semitonesFromA)
{ value = semitonesFromA; }
public static Note operator + (Note x, int semitones)
{
return new Note (x.value + semitones);
}
}
This overload allows us to add an int
to a Note
:
Note B = new Note (2); Note CSharp = B + 2;
Since we overrode +
, we can use
+=
too:
CSharp += 2;
Equality and comparison operators are often overridden when writing structs, and in rare cases with classes. Special rules and obligations come with overloading these operators:
The C# compiler enforces that operators that are logical
pairs are both defined. These operators are (== !=
), (<
>
), and (<=
>=
).
Equals
and GetHashCode
If you overload ==
and
!=
, you will usually need to
override object
’s Equals
and GetHashCode
methods, so that collections
and hashtables will work reliably with the type.
IComparable
and IComparable<T>
If you overload <
and
>
, you would also typically
implement IComparable
and
IComparable<T>
.
Extending the previous example, here’s how we could overload
Note
’s equality operators:
public static bool operator == (Note n1, Note n2) { return n1.value == n2.value; } public static bool operator != (Note n1, Note n2) { return !(n1.value == n2.value); } public override bool Equals (object otherNote) { if (!(otherNote is Note)) return false; return this == (Note)otherNote; } public override int GetHashCode() { return value.GetHashCode(); // Use value's hashcode }
Implicit and explicit conversions are overloadable operators. These conversions are typically overloaded to make converting between strongly related types (such as numeric types) concise and natural.
As explained in the discussion on types, the rationale behind implicit conversions is that they should always succeed and not lose information during conversion. Otherwise, explicit conversions should be defined.
In the following example, we define conversions between our
musical Note
type and a double (which
represents the frequency in hertz of that note):
... // Convert to hertz public static implicit operator double (Note x) { return 440 * Math.Pow (2,(double) x.value / 12 ); } // Convert from hertz (accurate to nearest semitone) public static explicit operator Note (double x) { return new Note ((int) (0.5 + 12 * (Math.Log(x/440) / Math.Log(2)) )); } ... Note n =(Note)554.37; // explicit conversion double x = n; // implicit conversion
This example is somewhat contrived: in real life, these
conversions might be better implemented with a ToFrequency
method and a (static) FromFrequency
method.