Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 50 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
50
Dung lượng
660,33 KB
Nội dung
420 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: n 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 expres- sion a + b * c is always the same as a + (b * c), regardless of the types of a, b, and c. n 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. n 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. n 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! n 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 method-like 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, the fol- lowing code shows 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 421 } public static Hour operator +(Hour lhs, Hour rhs) { return new Hour(lhs.value + rhs.value); } private int value; } Notice the following: n The operator is public. All operators must be public. n The operator is static. All operators must be static. Operators are never polymorphic and cannot use the virtual, abstract, override, or sealed modifier. n 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 your operator+ method. The C# compiler converts the code 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 422 Part III Creating Components 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, 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 instances 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 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 423 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 function- ality easier to use. Also, notice that you should not provide many different versions of this operator, each with a different second parameter type—instead, 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 operand. 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. 424 Part III Creating Components Operators and Language Interoperability Not all languages that execute using the common language runtime (CLR) support or understand operator overloading. 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 the same functionality. For example, suppose you implement operator+ for the Hour structure: public static Hour operator +(Hour lhs, int rhs) { } If you need to be able to use your class from a Visual Basic application, you should also provide an Add method that achieves the same thing: public static Hour Add(Hour lhs, int rhs) { } Understanding Compound Assignment Evaluation A compound assignment operator (such as +=) is always evaluated in terms of its associated operator (such as +). In other words, the statement a += b; is automatically evaluated like this: a = a + b; In general, the expression a @= b (where @ represents any valid operator) is always evalu- ated as a = a @ b. If you have overloaded the appropriate simple operator, the overloaded version is automatically called when you use its associated compound assignment operator. For example: Hour a = ; int b = ; a += a; // same as a = a + a a += b; // same as a = a + b The first compound assignment expression (a += a) is valid because a is of type Hour, and the Hour type declares a binary operator+ whose parameters are both Hour. Similarly, the second compound assignment expression (a += b) is also valid because a is of type Hour and b is of type int. The Hour type also declares a binary operator+ whose first parameter is an Hour and whose second parameter is an int. Note, however, that you cannot write the expression b += a because that’s the same as b = b + a. Although the addition is valid, the assignment is not, because there is no way to assign an Hour to the built-in int type. Chapter 21 Operator Overloading 425 Declaring Increment and Decrement Operators C# allows you to declare your own version of the increment (++) and decrement (––) operators. The usual rules apply when declaring these operators: they must be public, they must be static, and they must be unary (they can only take a single parameter). Here is the increment operator for the Hour structure: struct Hour { public static Hour operator ++(Hour arg) { arg.value++; return arg; } private int value; } The increment and decrement operators are unique in that they can be used in prefix and postfix forms. C# cleverly uses the same single operator for both the prefix and postfix ver- sions. The result of a postfix expression is the value of the operand before the expression takes place. In other words, the compiler effectively converts the code Hour now = new Hour(9); Hour postfix = now++; to this: Hour now = new Hour(9); Hour postfix = now; now = Hour.operator ++(now); // pseudocode, not valid C# The result of a prefix expression is the return value of the operator. The C# compiler effectively converts the code Hour now = new Hour(9); Hour prefix = ++now; to this: Hour now = new Hour(9); now = Hour.operator ++(now); // pseudocode, not valid C# Hour prefix = now; This equivalence means that the return type of the increment and decrement operators must be the same as the parameter type. 426 Part III Creating Components Comparing Operators in Structures and Classes Be aware that the implementation of the increment operator in the Hour structure works only because Hour is a structure. If you change Hour into a class but leave the implementa- tion of its increment operator unchanged, you will find that the postfix translation won’t give the correct answer. If you remember that a class is a reference type and revisit the compiler translations explained earlier, you can see why this occurs: Hour now = new Hour(9); Hour postfix = now; now = Hour.operator ++(now); // pseudocode, not valid C# If Hour is a class, the assignment statement postfix = now makes the variable postfix refer to the same object as now. Updating now automatically updates postfix! If Hour is a structure, the assignment statement makes a copy of now in postfix, and any changes to now leave postfix unchanged, which is what we want. The correct implementation of the increment operator when Hour is a class is as follows: class Hour { public Hour(int initialValue) { this.value = initialValue; } public static Hour operator ++(Hour arg) { return new Hour(arg.value + 1); } private int value; } Notice that operator ++ now creates a new object based on the data in the original. The data in the new object is incremented, but the data in the original is left unchanged. Although this works, the compiler translation of the increment operator results in a new object being creat- ed each time it is used. This can be expensive in terms of memory use and garbage collection overhead. Therefore, it is recommended that you limit operator overloads when you define types. This recommendation applies to all operators, and not just to the increment operator. Defining Operator Pairs Some operators naturally come in pairs. For example, if you can compare two Hour values by using the != operator, you would expect to be able to also compare two Hour values by using the == operator. The C# compiler enforces this very reasonable expectation by insist- ing that if you define either operator == or operator !=, you must define them both. This Chapter 21 Operator Overloading 427 neither-or-both rule also applies to the < and > operators and the <= and >= operators. The C# compiler does not write any of these operator partners for you. You must write them all explicitly yourself, regardless of how obvious they might seem. Here are the == and != operators for the Hour structure: struct Hour { public Hour(int initialValue) { this.value = initialValue; } public static bool operator ==(Hour lhs, Hour rhs) { return lhs.value == rhs.value; } public static bool operator !=(Hour lhs, Hour rhs) { return lhs.value != rhs.value; } private int value; } The return type from these operators does not actually have to be Boolean. However, you would have to have a very good reason for using some other type, or these operators could become very confusing! Note If you define operator == and operator != in a class, you should also override the Equals and GetHashCode methods inherited from System.Object (or System.ValueType if you are creating a structure). The Equals method should exhibit exactly the same behavior as operator ==. (You should define one in terms of the other.) The GetHashCode method is used by other classes in the Microsoft .NET Framework. (When you use an object as a key in a hash table, for example, the GetHashCode method is called on the object to help calculate a hash value. For more informa- tion, see the .NET Framework Reference documentation supplied with Visual Studio 2010.) All this method needs to do is return a distinguishing integer value. (Don’t return the same integer from the GetHashCode method of all your objects, however, because this will nullify the effectiveness of the hashing algorithms.) Implementing Operators In the following exercise, you will develop a class that simulates complex numbers. A complex number has two elements: a real component and an imaginary component. Typically, a complex number is represented in the form (x + yi), where x is the real compo- nent and yi is the imaginary component. The values of x and y are regular integers, and i 428 Part III Creating Components represents the square root of –1 (hence the reason why yi is imaginary). Despite their rather obscure and theoretical feel, complex numbers have a large number of uses in the fields of electronics, applied mathematics, physics, and many aspects of engineering. Note The .NET Framework 4.0 now includes a type called Complex in the System.Numerics namespace that implements complex numbers, so there is no real need to define your own implementation any more. However, it is still instructive to see how to implement some of the common operators for this type. You will implement complex numbers as a pair of integers that represent the coefficients x and y for the real and imaginary elements. You will also implement the operands necessary for performing simple arithmetic using complex numbers. The following table summarizes how to perform the four primary arithmetic operations on a pair of complex numbers, (a + bi) and (c + di). Operation Calculation (a + bi) + (c + di) ((a + c) + (b + d)i) (a + bi) – (c + di) ((a – c) + (b – d)i) (a + bi) * (c + di) (( a * c – b * d) + (b * c + a * d)i) (a + bi) / (c + di) ((( a * c + b * d) / ( c * c + d * d)) + ( b * c - a * d) / ( c * c + d * d))i) Create the Complex class, and implement the arithmetic operators 1. Start Microsoft Visual Studio 2010 if it is not already running. 2. Open the ComplexNumbers project, located in the \Microsoft Press\Visual CSharp Step By Step\Chapter 21\ComplexNumbers folder in your Documents folder. This is a console application that you will use to build and test your code. The Program.cs file contains the familiar DoWork method. 3. On the Project menu, click Add Class. In the Add New Item – Complex Numbers dialog box, type Complex.cs in the Name text box and then click Add. Visual Studio creates the Complex class and opens the Complex.cs file in the Code and Text Editor window. 4. Add the automatic integer properties Real and Imaginary to the Complex class, as shown next in bold. You will use these two properties to hold the real and imaginary components of a complex number. class Complex { public int Real { get; set; } public int Imaginary { get; set; } } Chapter 21 Operator Overloading 429 5. Add the constructor shown next in bold to the Complex class. This constructor takes two int parameters and uses them to populate the Real and Imaginary properties. class Complex { public Complex (int real, int imaginary) { this.Real = real; this.Imaginary = imaginary; } } 6. Override the ToString method as shown next in bold. This method returns a string representing the complex number in the form (x + yi). class Complex { public override string ToString() { return String.Format("({0} + {1}i)", this.Real, this.Imaginary); } } 7. Add the overloaded + operator shown next in bold to the Complex class. This is the binary addition operator. It takes two Complex objects and adds them together by performing the calculation shown in the table at the start of the exercise. The operator returns a new Complex object containing the results of this calculation. class Complex { public static Complex operator +(Complex lhs, Complex rhs) { return new Complex(lhs.Real + rhs.Real, lhs.Imaginary + rhs.Imaginary); } } 8. Add the overloaded – operator to the Complex class. This operator follows the same form as the overloaded + operator. class Complex { public static Complex operator -(Complex lhs, Complex rhs) { return new Complex(lhs.Real - rhs.Real, lhs.Imaginary - rhs.Imaginary); } } 9. Implement the * operator and / operator. These two operators follow the same form as the previous two operators, although the calculations are a little more complicated. [...]... already expanded) expand Visual C#, and then click Windows 2.3 In the middle pane, click the WPF Application icon Chapter 22 Introducing Windows Presentation Foundation 2.4 In the Location field, type \Microsoft Press \Visual CSharp Step By Step\ Chapter 22 under your Documents folder 2.5 In the Name field, type BellRingers 445 2.6 Click OK 3 If you are using Microsoft Visual C# 2010 Express, perform... form You have been using existing WPF applications in Microsoft Visual Studio 2010 in previous chapters, so much of the first couple of exercises will be a review for you Create the Middleshire Bell Ringers Association project 1 Start Visual Studio 2010 if it is not already running 2 If you are using Visual Studio 2010 Standard or Visual Studio 2010 Professional, perform the following operations to... explicit, followed by the operator keyword, followed by the type being converted to, followed by the type being converted from as a single p arameter between parentheses For example: class Complex { public static implicit operator Complex(int from) { // code to convert from an int } } Microsoft Visual C# 2010 Step by Step Part IV Building Windows Presentation Foundation Applications In this part: Introducing... left pane, under Installed Templates, click Visual C# 3.3 In the middle pane, click WPF Application; in the Name field, type BellRingers; and then click OK 3.4 When Visual Studio has created the project, on the File menu click Save All 3.5 In the Save Project dialog box, in the Location field specify the location Microsoft Press \Visual CSharp Step By Step\ Chapter 22 under your Documents folder,... you have completed it (You can see the completed version by building and running the BellRingers project in the \Microsoft Press \Visual CSharp Step By Step\ Chapter 22\BellRingers - Complete\ folder in your Documents folder.) Building the WPF Application In this exercise, you’ll start building the Middleshire Bell Ringers Association application by creating a new project, laying out the form, and adding... Visual Studio 2010 running, and turn to Chapter 22 n If you want to exit Visual Studio 2010 now On the File menu, click Exit If you see a Save dialog box, click Yes and save the project 440 Part III Creating Components Chapter 21 Quick Reference To Do this Implement an operator Write the keywords public and static, followed by the return type, f ollowed by the operator keyword, followed by the operator... be well versed in the C# language You have learned how to write programs and create components by using Microsoft C#, and you should understand many of the finer points of the language, such as extension methods, lambda expressions, and the distinction between value and reference types You now have the essential language skills, and in Part IV you will expand upon them and use C# to take advantage of... visible 4 In Solution Explorer, right-click the BellRingers project, point to Add, and then click Existing Item In the Add Existing Item – BellRingers dialog box, move to the folder Microsoft Press \Visual CSharp Step By Step\ Chapter 22 under your Documents folder In the drop-down list box adjacent to the File name text box, select All Files (*.*) Select the file bell.gif, and then click Add This action... the Microsoft NET Framework In particular, you will see how to use the objects in the System.Windows namespace to create WPF applications In this chapter, you learn how to build a basic WPF application by using the common c omponents that are a feature of most GUI applications You see how to set the properties of WPF forms and controls by using the Design View and Properties windows, and also by using... attributes specify the default height and width of the form You can modify these values either by changing them in the XAML pane or by using the Properties window You can also 446 Part IV Building Windows Presentation Foundation Applications change the value of these and many other properties dynamically by writing C# code that executes when the form runs 2 Click the MainWindow form in the Design View . operators 1. Start Microsoft Visual Studio 2010 if it is not already running. 2. Open the ComplexNumbers project, located in the Microsoft Press Visual CSharp Step By Step Chapter 21ComplexNumbers. Hour values by using the != operator, you would expect to be able to also compare two Hour values by using the == operator. The C# compiler enforces this very reasonable expectation by insist- ing. operators. n If you want to continue to the next chapter Keep Visual Studio 2010 running, and turn to Chapter 22. n If you want to exit Visual Studio 2010 now On the File menu, click Exit. If you see a