Tải bản đầy đủ (.pdf) (50 trang)

Microsoft Visual C# 2010 Step by Step (P10) pptx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (660.33 KB, 50 trang )

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.
430 Part III Creating Components
(The calculation for the / operator has been broken down into two steps to avoid
lengthy lines of code.)
class Complex
{

public static Complex operator *(Complex lhs, Complex rhs)
{
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.
Chapter 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 equal-
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.
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.
class Complex
{

public static bool operator ==(Complex lhs, Complex rhs)
{
return lhs.Equals(rhs);
}

public static bool operator !=(Complex lhs, Complex rhs)
{
return !(lhs.Equals(rhs));
}
}
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.
432 Part III Creating Components
Note 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:
class Complex
{

public override bool Equals(Object obj)
{
if (obj is Complex)
{
Complex compare = (Complex)obj;
return (this.Real == compare.Real) &&
(this.Imaginary == compare.Imaginary);
}
else
{
return false;
}
}
}
The Equals method takes an Object as a parameter. This code verifies that the type of
the parameter is actually a Complex object. If it is, this code compares the values in
the Real and Imaginary properties in the current instance and the parameter passed
in; if they are the same, the method returns true, or it returns false otherwise. If the
parameter passed in is not a Complex object, the method returns false.
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.
Chapter 21 Operator Overloading 433
Class Complex
{

public override int GetHashCode()
{
return base.GetHashCode();
}
}
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()
{


if (temp == first)
{
Console.WriteLine("Comparison: temp == first");
}
else
{
Console.WriteLine("Comparison: temp != first");
}

if (temp == temp)
{
Console.WriteLine("Comparison: temp == temp");
}
else
{
Console.WriteLine("Comparison: temp != temp");
}
}
Note The expression temp == temp generates a warning message “Comparison made to
same variable: did you mean to compare to something else?” In this case, you can ignore
the warning because this comparison is intentional; it is to verify that the == operator is
working as expected.
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.
434 Part III Creating Components
Understanding 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:
class Example
{
public static void MyDoubleMethod(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:
class Example
{
public static void MyIntMethod(int parameter)
{

}

}

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);
Chapter 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:
struct Hour
{

public static implicit operator int (Hour from)
{
return this.value;
}

private int value;
}
The type you are converting from is declared as the single parameter (in this case, Hour), and
the type you are converting to is declared as the type name after the keyword operator (in

this case, int). There is no return type specified before the keyword operator.
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
436 Part III Creating Components
When 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 con-
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?)
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 ear-
lier, you can provide a single version of operator+ (that takes two Hour parameters) and an

implicit int to Hour conversion, like this:
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 implicit operator Hour (int from)
{
return new Hour (from);
}

private int value;
}
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
}
Chapter 21 Operator Overloading 437
Writing 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
property is set to 0.
class Complex
{

public Complex(int real)
{
this.Real = real;
this.Imaginary = 0;
}

}
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.
class Complex
{

public static implicit operator Complex(int from)
{
return new Complex(from);

}
}
438 Part III Creating Components
3. 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.
class Complex
{

public static explicit operator int(Complex from)
{
return from.Real;
}
}
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()
{

Console.WriteLine("Current value of temp is {0}", temp);

if (temp == 2)
{
Console.WriteLine("Comparison after conversion: temp == 2");
}
else
{
Console.WriteLine("Comparison after conversion: temp != 2");
}


temp += 2;
Console.WriteLine("Value after adding 2: temp = {0}", temp);
}
These statements test the implicit operator that converts an int to a Complex object.
The if statement compares a Complex object to an int. The compiler generates code
that converts the int into a Complex object first, and then invokes the == operator
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.
Chapter 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.
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,
followed by the operator keyword, followed by the operator symbol
being declared, followed by the appropriate parameters between paren-
theses. Implement the logic for the operator in the body of the method.
For example:
class Complex
{

public static bool operator==(Complex lhs, Complex rhs)
{
// Implement logic for == operator
}


}
Define a conversion operator Write the keywords public and static, followed by the keyword implicit
or explicit, followed by the operator keyword, followed by the type be-
ing converted to, followed by the type being converted from as a single
parameter 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
441
Part IV
Building Windows Presentation
Foundation Applications
In this part:
Introducing Windows Presentation Foundation . . . . . . . . . . . . . . . . . . . . . . . . . . 443
Gathering User Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
Performing Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
443
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.
Creating a WPF Application
As an example, you are going to create an application that a user can use to input and
display details for members of the Middleshire Bell Ringers Association, an esteemed group
of the finest campanologists. Initially, you will keep the application very simple, concentrat-

ing on laying out the form and making sure that it all works. On the way, you learn about
some of the features that WPF provides for building highly adaptable user interfaces. In
444 Part IV Building Windows Presentation Foundation Applications
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.

×