Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C# 2008 Step by Step.pdf
Скачиваний:
22
Добавлен:
25.03.2016
Размер:
13.96 Mб
Скачать

Chapter 21

Operator Overloading

After completing this chapter, you will be able to:

Implement binary operators for your own types.

Implement unary operators for your own types.

Write increment and decrement operators for your own types.

Understand the need to implement some operators as pairs.

Implement implicit conversion operators for your own types.

Implement explicit conversion operators for your own types.

You have made a great deal of use of the standard operator symbols (such as + and ) to perform standard operations (such as addition and subtraction) on types (such as int and double).

Many of the built-in types come with their own predefined behaviors for each operator. You can also define how operators should behave for your own structures and classes, which is the subject of this chapter.

Understanding Operators

You use operators to combine operands together into expressions. Each operator has its own semantics, dependent on the type it works with. For example, the + operator means “add” when used with numeric types or “concatenate” when used with strings.

Each operator symbol has a precedence. For example, the * operator has a higher precedence than the + operator. This means that the expression a + b * c is the same as a + (b * c).

Each operator symbol also has an associativity to define whether the operator evaluates from left to right or from right to left. For example, the = operator is right-associative (it evaluates from right to left), so a = b = c is the same as a = (b = c).

Aunary operator is an operator that has just one operand. For example, the increment operator (++) is a unary operator.

Abinary operator is an operator that has two operands. For example, the multiplication operator (*) is a binary operator.

395

396 Part III Creating Components

Operator Constraints

You have seen throughout this book that C# enables you to overload methods when defining your own types. C# also allows you to overload many of the existing operator symbols for your own types, although the syntax is slightly different. When you do this, the operators you implement automatically fall into a well-defined framework with the following rules:

You cannot change the precedence and associativity of an operator. The precedence and associativity are based on the operator symbol (for example, +) and not on the

type (for example, int) on which the operator symbol is being used. Hence, the expression a + b * c is always the same as a + (b * c), regardless of the types of a, b, and c.

You cannot change the multiplicity (the number of operands) of an operator. For example, * (the symbol for multiplication), is a binary operator. If you declare a * operator for your own type, it must be a binary operator.

You cannot invent new operator symbols. For example, you can’t create a new operator symbol, such as ** for raising one number to the power of another number. You’d have to create a method for that.

You can’t change the meaning of operators when applied to built-in types. For example, the expression 1 + 2 has a predefined meaning, and you’re not allowed to override this meaning. If you could do this, things would be too complicated!

There are some operator symbols that you can’t overload. For example, you can’t overload the dot (.) operator, which indicates access to a class member. Again, if you could do this, it would lead to unnecessary complexity.

Tip You can use indexers to simulate [ ] as an operator. Similarly, you can use properties to simulate assignment (=) as an operator, and you can use delegates to simulate a function call as an operator.

Overloaded Operators

To define your own operator behavior, you must overload a selected operator. You use

methodlike syntax with a return type and parameters, but the name of the method is the keyword operator together with the operator symbol you are declaring. For example, here’s

a user-defined structure named Hour that defines a binary + operator to add together two instances of Hour:

struct Hour

{

public Hour(int initialValue)

{

this.value = initialValue;

}

Chapter 21 Operator Overloading

397

public static Hour operator+ (Hour lhs, Hour rhs)

{

return new Hour(lhs.value + rhs.value);

}

...

private int value;

}

Notice the following:

The operator is public. All operators must be public.

The operator is static. All operators must be static. Operators are never polymorphic and cannot use the virtual, abstract, override, or sealed modifier.

A binary operator (such as the + operator, shown earlier) has two explicit arguments, and a unary operator has one explicit argument. (C++ programmers should note that operators never have a hidden this parameter.)

Tip When declaring highly stylized functionality (such as operators), it is useful to adopt a naming convention for the parameters. For example, developers often use lhs and rhs (acronyms for left-hand side and right-hand side, respectively) for binary operators.

When you use the + operator on two expressions of type Hour, the C# compiler automatically converts your code to a call to the user-defined operator. The C# compiler converts this:

Hour Example(Hour a, Hour b)

{

return a + b;

}

to this:

Hour Example(Hour a, Hour b)

{

return Hour.operator+(a,b); // pseudocode

}

Note, however, that this syntax is pseudocode and not valid C#. You can use a binary operator only in its standard infix notation (with the symbol between the operands).

There is one final rule that you must follow when declaring an operator (otherwise, your code

will not compile): at least one of the parameters must always be of the containing type. In the preceding operator+ example for the Hour class, one of the parameters, a or b, must be

an Hour object. In this example, both parameters are Hour objects. However, there could be times when you want to define additional implementations of operator+ that add, for ex-

ample, an integer (a number of hours) to an Hour object—the first parameter could be Hour,

398

Part III Creating Components

and the second parameter could be the integer. This rule makes it easier for the compiler to know where to look when trying to resolve an operator invocation, and it also ensures that you can’t change the meaning of the built-in operators.

Creating Symmetric Operators

In the preceding section, you saw how to declare a binary + operator to add together two in-

stances of type Hour. The Hour structure also has a constructor that creates an Hour from an int. This means that you can add together an Hour and an int—you just have to first use the Hour constructor to convert the int to an Hour. For example:

Hour a = ...; int b = ...;

Hour sum = a + new Hour(b);

This is certainly valid code, but it is not as clear or as concise as adding together an Hour and an int directly, like this:

Hour a = ...; int b = ...;

Hour sum = a + b;

To make the expression (a + b) valid, you must specify what it means to add together an Hour (a, on the left) and an int (b, on the right). In other words, you must declare a binary + operator whose first parameter is an Hour and whose second parameter is an int. The

following code shows the recommended approach:

struct Hour

{

public Hour(int initialValue)

{

this.value = initialValue;

}

...

public static Hour operator+ (Hour lhs, Hour rhs)

{

return new Hour(lhs.value + rhs.value);

}

public static Hour operator+ (Hour lhs, int rhs)

{

return lhs + new Hour(rhs);

}

...

private int value;

}

Chapter 21 Operator Overloading

399

Notice that all the second version of the operator does is construct an Hour from its int

argument and then call the first version. In this way, the real logic behind the operator is held in a single place. The point is that the extra operator+ simply makes existing functionality eas-

ier to use. Also, notice that you should not provide many different versions of this operator, each with a different second parameter type—cater to the common and meaningful cases only, and let the user of the class take any additional steps if an unusual case is required.

This operator+ declares how to add together an Hour as the left-hand operand and an int as the right-hand operator. It does not declare how to add together an int as the left-hand operand and an Hour as the right-hand operand:

int a = ...; Hour b = ...;

Hour sum = a + b; // compile-time error

This is counterintuitive. If you can write the expression a + b, you expect to also be able to write b + a. Therefore, you should provide another overload of operator+:

struct Hour

{

public Hour(int initialValue)

{

this.value = initialValue;

}

...

public static Hour operator+ (Hour lhs, int rhs)

{

return lhs + new Hour(rhs);

}

public static Hour operator+ (int lhs, Hour rhs)

{

return new Hour(lhs) + rhs;

}

...

private int value;

}

Note C++ programmers should notice that you must provide the overload yourself. The compiler won’t write the overload for you or silently swap the sequence of the two operands to find a matching operator.

Operators and Language Interoperability

Not all languages that execute using the common language runtime (CLR) support or understand operator overloading. Microsoft Visual Basic is a common example. If you are creating classes that you want to be able to use from other languages, if you overload an operator, you should provide an alternative mechanism that supports

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]