In other words, the compiler effectively converts the code Hour now = new Hour9; Hour postfix = now++; to this: Hour now = new Hour9; Hour postfix = now; now = Hour.operator ++now; //
Trang 1Operator 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
Trang 2Chapter 21 Operator Overloading 421
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)
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
Trang 3times when you want to define additional implementations of operator+ that add, for ample, an integer (a number of hours) to an Hour object—the first parameter could be Hour,
ex-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:
following code shows the recommended approach:
Trang 4Chapter 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+:
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
Trang 5Operators 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 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
Trang 6Chapter 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:
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
Trang 7Comparing 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:
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
Trang 8Chapter 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:
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 nent and yi is the imaginary component The values of x and y are regular integers, and i
Trang 9compo-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; }
}
Trang 10Chapter 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
6 Override the ToString method as shown next in bold This method returns a string
representing the complex number in the form (x + yi)
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
8 Add the overloaded – operator to the Complex class This operator follows the same
form as the overloaded + operator
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
Trang 11(The calculation for the / operator has been broken down into two steps to avoid lengthy lines of code )
return new Complex(lhs.Real * rhs.Real + lhs.Imaginary * rhs.Real,
lhs.Imaginary * rhs.Imaginary + lhs.Real * rhs.Imaginary); }
public static Complex operator /(Complex lhs, Complex rhs)
{
int realElement = (lhs.Real * rhs.Real + lhs.Imaginary * rhs.Imaginary) / (rhs.Real * rhs.Real + rhs.Imaginary * rhs.Imaginary); int imaginaryElement = (lhs.Imaginary * rhs.Real - lhs.Real * rhs.Imaginary) / (rhs.Real * rhs.Real + rhs.Imaginary * rhs.Imaginary); return new Complex(realElement, imaginaryElement);
}
}
10 Display the Program cs file in the Code and Text Editor window Add the following
statements shown in bold to the DoWork method of the Program class:
static void DoWork()
{
Complex first = new Complex(10, 4);
Complex second = new Complex(5, 2);
Console.WriteLine("first is {0}", first);
Console.WriteLine("second is {0}", second);
Complex temp = first + second;
Console.WriteLine("Add: result is {0}", temp);
temp = first - second;
Console.WriteLine("Subtract: result is {0}", temp);
temp = first * second;
Console.WriteLine("Multiply: result is {0}", temp);
temp = first / second;
Console.WriteLine("Divide: result is {0}", temp);
}
This code creates two Complex objects that represent the complex values (10 + 4i) and
(5 + 2i) The code displays them, and then tests each of the operators you have just defined, displaying the results in each case
11 On the Debug menu, click Start Without Debugging
Verify that the application displays the results shown in the following image
Trang 12Chapter 21 Operator Overloading 431
12 Close the application, and return to the Visual Studio 2010 programming environment
You have now created a type that models complex numbers and supports basic arithmetic
operations In the next exercise, you will extend the Complex class and provide the ity operators, == and != Remember that if you implement these operators you should also override the Equals and GetHashCode methods that the class inherits from the Object type
equal-Implement the equality operators
1 In Visual Studio 2010, display the Complex cs file in the Code and Text Editor window
2 Add the == and != operators to the Complex class as shown next in bold Notice that
these operators both make use of the Equal method The Equal method compares an instance of a class against another instance specified as an argument It returns true if they are equal and false otherwise
3 On the Build menu, click Rebuild Solution
The Error List window displays the following warning messages:
'ComplexNumbers.Complex' defines operator == or operator != but does not override Object.GetHashCode()
'ComplexNumbers.Complex' defines operator == or operator != but does not override Object.Equals(object o)
If you define the != and == operators, you should also override the Equal and
GetHashCode methods inherited from SystemObject
Trang 13Note If the Error List window is not displayed, on the View menu, click Error List
4 Override the Equals method in the Complex class as shown next in bold:
Complex compare = (Complex)obj;
return (this.Real == compare.Real) &&
Important It is tempting to write the Equals method like this:
public override bool Equals(Object obj) {
Complex compare = obj As Complex;
if (compare != null) {
return (this.Real == compare.Real) &&
(this.Imaginary == compare.Imaginary);
} else { return false;
} } However, the expression compare != null invokes the != operator of the Complex class,
which calls the Equals method again, resulting in an infinitely recursive loop
5 Override the GetHashCode method This implementation simply calls the method
inherited from the Object class, but you can provide your own mechanism to generate
a hash code for an object if you prefer
Trang 14Chapter 21 Operator Overloading 433
6 On the Build menu, click Rebuild Solution
Verify that the solution now builds without reporting any warnings
7 Display the Program cs file in the Code and Text Editor window Add the following code
to the end of the DoWork method:
static void DoWork()
8 On the Debug menu, click Start Without Debugging Verify that the final two messages
displayed are these:
Comparison: temp != first
Comparison: temp == temp
9 Close the application, and return to Visual Studio 2010
Trang 15Understanding Conversion Operators
Sometimes you need to convert an expression of one type to another For example, the
following method is declared with a single double parameter:
You might reasonably expect that only values of type double could be used as
argu-ments when calling MyDoubleMethod, but this is not so The C# compiler also allows MyDoubleMethod to be called with an argument whose type is not double, but only if that value can be converted to a double For example, you can provide an int argument In that case, the compiler generates code that converts the argument from an int to a double when
the method is called
Providing Built-in Conversions
The built-in types have some built-in conversions For example, as mentioned previously, an
int can be implicitly converted to a double An implicit conversion requires no special syntax
and never throws an exception:
Example.MyDoubleMethod(42); // implicit int-to-double conversion
An implicit conversion is sometimes called a widening conversion because the result is wider
than the original value—it contains at least as much information as the original value, and nothing is lost
On the other hand, a double cannot be implicitly converted to an int:
Example.MyIntMethod(42.0); // compile-time error
When you convert a double to an int, you run the risk of losing information, so the
conver-sion will not be performed automatically (Consider what would happen if the argument to
MyIntMethod were 42 5—how should this be converted?) A double can be converted to an int, but the conversion requires an explicit notation (a cast):
Example.MyIntMethod((int)42.0);
Trang 16Chapter 21 Operator Overloading 435
An explicit conversion is sometimes called a narrowing conversion because the result is narrower than the original value (that is, it can contain less information) and can throw an OverflowException C# allows you to provide conversion operators for your own user-defined
types to control whether it is sensible to convert values to other types and whether these conversions are implicit or explicit
Implementing User-Defined Conversion Operators
The syntax for declaring a user-defined conversion operator is similar to that for declaring an
overloaded operator A conversion operator must be public and must also be static Here’s a conversion operator that allows an Hour object to be implicitly converted to an int:
When declaring your own conversion operators, you must specify whether they are implicit
conversion operators or explicit conversion operators You do this by using the implicit and explicit keywords For example, the Hour to int conversion operator mentioned earlier is
implicit, meaning that the C# compiler can use it implicitly (without requiring a cast):
class Example
{
public static void MyOtherMethod(int parameter) { }
public static void Main()
{
Hour lunch = new Hour(12);
Example.MyOtherMethod(lunch); // implicit Hour to int conversion
}
}
If the conversion operator had been declared explicit, the preceding example would not have
compiled, because an explicit conversion operator requires an explicit cast:
Example.MyOtherMethod((int)lunch); // explicit Hour to int conversion
Trang 17When should you declare a conversion operator as explicit or implicit? If a conversion is always safe, does not run the risk of losing information, and cannot throw an exception, it
can be defined as an implicit conversion Otherwise, it should be declared as an explicit version Converting from an Hour to an int is always safe—every Hour has a corresponding int value—so it makes sense for it to be implicit An operator that converts a string to an Hour should be explicit because not all strings represent valid Hours (The string “7” is fine, but how would you convert the string “Hello, World” to an Hour?)
con-Creating Symmetric Operators, Revisited
Conversion operators provide you with an alternative way to resolve the problem of providing symmetric operators For example, instead of providing three versions of
operator+ (Hour + Hour, Hour + int, and int + Hour) for the Hour structure, as shown lier, you can provide a single version of operator+ (that takes two Hour parameters) and an implicit int to Hour conversion, like this:
If you add an Hour to an int (in either order), the C# compiler automatically converts the int
to an Hour and then calls operator+ with two Hour arguments:
void Example(Hour a, int b)
{
Hour eg1 = a + b; // b converted to an Hour
Hour eg2 = b + a; // b converted to an Hour
}
Trang 18Chapter 21 Operator Overloading 437Writing Conversion Operators
In the following exercise, you will add further operators to the Complex class You will start
by writing a pair of conversion operators that convert between the int type and the Complex type Converting an int to a Complex object is always a safe process and never loses informa- tion (because an int is really just a Complex number without an imaginary element) So you
will implement this as an implicit conversion operator However, the converse is not true; to
convert a Complex object into an int, you have to discard the imaginary element So you will
implement this conversion operator as explicit
Implement the conversion operators
1 Return to Visual Studio 2010 and display the Complex cs file in the Code and Text Editor
window Add the constructor shown next in bold to the Complex class This constructor takes a single int parameter which it uses to initialize the Real property The imaginary
2 Add the following implicit conversion operator to the Complex class This operator
converts from an int to a Complex object by returning a new instance of the Complex
class by using the constructor you created in the previous step
Trang 193 Add the explicit conversion operator shown next to the Complex class This operator
takes a Complex object and returns the value of the Real property This conversion
discards the imaginary element of the complex number
4 Display the Program cs file in the Code and Text Editor window Add the following code
to the end of the DoWork method:
static void DoWork()
of the Complex class The statement that adds 2 to the temp variable converts the int value 2 into a Complex object and then uses the + operator of the Complex class
5 Add the following statements to end of the DoWork method:
static void DoWork()
{
int tempInt = temp;
Console.WriteLine("Int value after conversion: tempInt = {0}", tempInt);
}
The first statement attempts to assign a Complex object to an int variable
Trang 20Chapter 21 Operator Overloading 439
6 On the Build menu, click Rebuild Solution
The solution fails to build, and the compiler reports the following error in the Error List
window:
Cannot implicitly convert type 'ComplexNumbers.Complex' to 'int' An explicit
conversion exists (are you missing a cast?)
The operator that converts from a Complex object to an int is an explicit conversion
operator, so you must specify a cast
7 Modify the statement that attempts to store a Complex value in an int variable to use a
cast, like this:
int tempInt = (int)temp;
8 On the Debug menu, click Start Without Debugging Verify that the solution now builds,
and that the final four statements output are these:
Current value of temp is (2 + 0i)
Comparison after conversion: temp == 2
Value after adding 2: temp = (4 + 0i)
Int value after conversion: tempInt = 4
9 Close the application, and return to Visual Studio 2010
In this chapter, you learned how to overload operators and provide functionality specific to a class or structure You implemented a number of common arithmetic operators, and you also created operators that enable you to compare instances of a class Finally, you learned how
to create implicit and explicit conversion 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 Save dialog box, click Yes and save the project
Trang 21Chapter 21 Quick Reference
Implement an operator Write the keywords public and static, followed by the return type,
followed by the operator keyword, followed by the operator symbol
being declared, followed by the appropriate parameters between theses Implement the logic for the operator in the body of the method For example:
paren-class Complex {
or explicit, followed by the operator keyword, followed by the type
be-ing converted to, followed by the type bebe-ing converted from as a sbe-ingle parameter between parentheses For example:
class Complex {
Trang 22Microsoft Visual C# 2010 Step by Step
Trang 24443
Chapter 22
Introducing Windows Presentation Foundation
After completing this chapter, you will be able to:
n Create Microsoft Windows Presentation Foundation (WPF) applications
n Use common WPF controls such as labels, text boxes, and buttons
n Define styles for WPF controls
n Change the properties of WPF forms and controls at design time and through code at run time
n Handle events exposed by WPF forms and controls
Now that you have completed the exercises and examined the examples in the first three parts of this book, you should 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 lan-guage skills, and in Part IV you will expand upon them and use C# to take advantage of the graphical user interface (GUI) libraries provided as part of 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
components 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
Extensible Application Markup Language, or XAML You also learn how to use WPF styles to build user interfaces that can be easily adapted to conform to your organization’s presenta-tion standards Finally, you learn how to intercept and handle some of the events that WPF forms and controls expose
Trang 25concentrat-later chapters, you will provide menus and learn how to implement validation to ensure that the data that is entered makes sense The following graphic shows what the application will look like after 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 controls to the 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 create a new WPF application:
2 1 On the File menu, point to New, and then click Project
The New Project dialog box opens
2 2 In the left pane, expand Installed Templates (if it is not already expanded) expand
Visual C#, and then click Windows
2 3 In the middle pane, click the WPF Application icon