C# lets you overload operators to
work
with operands that are custom classes or structs using operators. An
operator
is
a static method with the keyword operator
preceding the operator to overload (instead of a method name),
parameters representing the operands, and return types representing
the result of an expression. Table 4-1 lists the
available overloadable operators.
Table 4-1. Overloadable operators
|
|
|
|
|
|
|
|
|
|
|
& (binary only) |
|
|
|
|
|
|
|
|
|
|
Literals that also act as overloadable operators are
true
and false
.
A pair of references exhibit
referential
equality when both references point to the same object. By default,
the = =
and !=
operators will
compare two reference-type variables by reference. However, it is
occasionally more natural for the = =
and
!=
operators to exhibit value equality, whereby
the comparison is based on the value of the objects that the
references point to.
Whenever overloading the = =
and
!=
operators, you should always override the
virtual Equals
method to route its functionality
to the = =
operator. This allows a class to be
used polymorphically (which is essential if you want to take
advantage of functionality such as the collection classes). It also
provides compatibility with other .NET languages that
don’t overload operators.
Tip
A good guideline for knowing whether to implement the = =
and !=
operators is if it is natural
for the class to overload other operators too, such as
<
, >
,
+
, or -
; otherwise,
don’t bother—just implement the
Equals
method. For structs, overloading the
= =
and !=
operators provides a
more efficient implementation than the default one.
class Note { int value; public Note(int semitonesFromA) { value = semitonesFromA; } public static bool operator = =(Note x, Note y) { return x.value = = y.value; } public static bool operator !=(Note x, Note y) { return x.value != y.value; } public override bool Equals(object o) { if(!(o is Note)) return false; return this = =(Note)o; } } Note a = new Note(4); Note b = new Note(4); Object c = a; Object d = b; // To compare a and b by reference Console.WriteLine((object)a = =(object)b; // false //To compare a and b by value: Console.WriteLine(a = = b); // true //To compare c and d by reference: Console.WriteLine(c = = d); // false //To compare c and d by value: Console.WriteLine(c.Equals(d)); // true
The C# compiler enforces operators
that are logical pairs to both be
defined. These operators are = = !=
, < >
, and <= >=
.
As explained in the discussion
on
types, the rationale behind implicit conversions is they are
guaranteed to succeed and do not lose information during the
conversion. Conversely, an explicit conversion is required either
when runtime circumstances will determine whether the conversion will
succeed or if information may be lost during the conversion. In this
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(only 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
The true
and false
keywords are
used as operators when defining
types with three-state logic to enable these types to work seamlessly
with constructs that take boolean expressions—namely, the
if
, do
,
while
, for
, and conditional
(?:)
statements. The
System.Data.SQLTypes.SQLBoolean
struct provides
this functionality:
public struct SQLBoolean ... { ... public static bool operator true(SQLBoolean x) { return x.value = = 1; } public static bool operator false(SQLBoolean x) { return x.value = = -1; } public static SQLBoolean operator !(SQLBoolean x) { return new SQLBoolean(- x.value); } public bool IsNull { get { return value = = 0;} } ... } class Test { void Foo(SQLBoolean a) { if (a) Console.WriteLine("True"); else if (! a) Console.WriteLine("False"); else Console.WriteLine("Null"); } }
The &&
and ||
operators
are
automatically evaluated from
&
and |
, so they do not
need to be overloaded. The [ ]
operators can be
customized with indexers (see Section 3.1.5 in Chapter 3). The assignment operator =
cannot be overloaded, but all other assignment operators are
automatically evaluated from their corresponding binary operators
(e.g., +=
is evaluated from +
).
Get C# in a Nutshell, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.