C# ПІДРУЧНИКИ / c# / Apress - Accelerated C# 2005
.pdf
384 C H A P T E R 1 3 ■ I N S E A R C H O F C # C A N O N I C A L F O R M S
return result;
}
public override int GetHashCode() { return (int) this.Magnitude;
}
public static bool operator ==( ComplexNumber num1, ComplexNumber num2 ) {
return num1.Equals(num2);
}
public static bool operator !=( ComplexNumber num1, ComplexNumber num2 ) {
return !num1.Equals(num2);
}
public int CompareTo( object other ) { if( !(other is ComplexNumber) ) {
throw new ArgumentException( "Bad Comparison!" );
}
return CompareTo( (ComplexNumber) other );
}
public int CompareTo( ComplexNumber that ) { int result;
if( Equals(that) ) { result = 0;
}else if( this.Magnitude > that.Magnitude ) { result = 1;
}else {
result = -1;
}
return result;
}
public double Magnitude { get {
return Math.Sqrt( Math.Pow(this.real, 2) + Math.Pow(this.imaginary, 2) );
}
}
// Other methods removed for clarity
private readonly double real; private readonly double imaginary;
}
public sealed class EntryPoint
{
static void Main()
{
ComplexNumber num1 = new ComplexNumber( 1, 2 ); ComplexNumber num2 = new ComplexNumber( 1, 2 );
C H A P T E R 1 3 ■ I N S E A R C H O F C # C A N O N I C A L F O R M S |
385 |
bool result = num1.Equals( num2 );
}
}
Now, the comparison inside Main() is much more efficient, since the value doesn’t need to be boxed. The compiler chooses the closest match of the two overloads, which, of course, is the strongly typed overload of Equals() that accepts a ComplexNumber rather than a generic object type. Internally, the Object.Equals() override delegates to the type-safe version of Equals() after it checks the type of the object and unboxes it. It’s important to note that the Object.Equals() override first checks the type to see if it is a ComplexNumber, or more specifically a boxed ComplexNumber, before it unboxes it so as to avoid throwing an exception. The Standard Library documentation for Object.Equals() clearly states that overrides of Object.Equals() must not throw exceptions. Finally, notice that the same rule of thumb for GetHashCode() exists for structs as well as classes. If you override Object. Equals(), you must also override Object.GetHashCode(), or vice versa.
Note that I also implemented IComparable<ComplexNumber>, which uses the same technique as IEquatable<ComplexNumber> to provide a type-safe version of IComparable. You should always consider implementing these generic interfaces so the compiler has greater latitude when enforcing type safety.
Do Values of This Type Support Any Interfaces?
The difference in behavior between value types and reference types within the CLR can sometimes cause headaches and confusion, especially to those who are new to the CLR and C#. Those headaches usually derive from the tricky nature of bridging the two worlds between reference types and value types. Consider the fact that all value types (structs) implicitly derive from System.ValueType. Also, consider the fact that System.ValueType derives from System.Object. You might be inclined to think that you could simply cast a value type, such as an instance of ComplexNumber, into Object and thus, bridge the gap between the value-type world and the reference-type world. This is what happens, but probably not as you may expect.
What actually happens is that the CLR creates a new object for you, and that new object contains a copy of your value type. You have already seen this concept defined as boxing. Under the covers, when the CLR encounters a definition for a struct, or value type, it also internally defines a reference type, which is the box I’m talking about when I talk about a boxing operation. You can’t create an instance of that type explicitly, but that’s what you’re doing when you incur a boxing operation on a value instance.
When the CLR creates this internal boxing type at run time, it uses reflection to implement all of the methods that your value type implements, and the method implementations simply forward the calls to the contained copy of your value type. By the same token, the dynamically generated boxing type also implements any interfaces that the value type implements. Thus, references to instances of the dynamic box type, which is a reference type, can be cast to references of the implemented interface types, as is natural for reference types. But what do you think happens when you cast a value type instance into an interface type? The answer is that the value must be boxed first. It makes sense when you consider that an interface reference always references a reference type.
You’ve already seen how boxing can be a nuisance in C#. This is because boxing happens automatically, as if to help you out. But unless you know what’s going on under the covers, it can cause more confusion than not, since you can inadvertently modify a value within a box and then throw it away without propagating those changes back into the original value from the boxed value. Dizzying, isn’t it?
Can you think of a way whereby you can modify a value that lives inside a box? If you cast the box instance back to its value type, you get a new copy of the value in the box. So, that cannot do the trick. What you need is a way to touch the internal boxed value. Interfaces are the answer. As I said before, the internally created boxing reference type that you never see implements all of the interfaces that the struct implements. Since interface references refer to objects, they can modify the state of
386C H A P T E R 1 3 ■ I N S E A R C H O F C # C A N O N I C A L F O R M S
the value inside the box, if you make calls through the interface. You cannot modify the contents of a value within a box except through an interface.
In closing, it’s important to note that value types that implement interfaces will incur implicit boxing if you cast one of those types to an interface type that it implements. At the same time, interfaces are the only mechanism through which you can change the value of a value inside of a box. For an example of how to do this, check out the “Boxing and Unboxing” section within Chapter 4.
Implement Type-Safe Forms of Interface Members and Derived
Methods
I already covered this topic with respect to reference types in the “Prefer Type Safety at All Times” section. Most of those same points are applicable to value types, along with some added efficiency considerations. These efficiency problems stem from explicit conversion operations from value types to reference types, and vice versa. As you know, these conversions produce hidden boxing and unboxing operations in the generated IL code. Boxing operations can easily kill your efficiency in many situations. The points made previously about how type-safe versions of the enumeration methods help the C# compiler create much more efficient code in a foreach loop apply tenfold to value types. That is because boxing operations from conversions to and from value types take much more processor time when compared to a typecast of a reference type, which is relatively quick.
You’ve already seen how the ComplexNumber value type implements an interface—in this case, IComparable. That is because you still want value types to be sortable if they’re stored within a container. You’ll notice that core types within the CLR, such as System.Int32, also support interfaces such as IComparable. However, from an efficiency standpoint, you don’t want to box a value type each time you want to compare it to another. In fact, as it is currently written, the following code boxes both values:
public void Main()
{
ComplexNumber num1 = new ComplexNumber( 1, 3 ); ComplexNumber num2 = new ComplexNumber( 1, 2 );
int result = ((IComparable)num1).CompareTo( num2 );
}
Can you see both of the boxing operations? As was shown in the previous section, the num1 instance must be boxed in order to acquire a reference to the IComparable interface on it. Secondly, since CompareTo() accepts a reference of type System.Object, the num2 instance must be boxed. This is terrible for efficiency. Technically, I didn’t have to box num1 in order to call through IComparable.
However, if the previous ComplexNumber example had implemented the IComparable interface explicitly, I would have had no choice.
To solve this problem, you want to implement a type-safe version of the CompareTo method, while at the same time implementing the IComparable.CompareTo method. Using this technique, the comparison call in the previous code will incur absolutely no boxing operations. Let’s look at how to modify the ComplexNumber struct to do this:
using System;
public struct ComplexNumber : IComparable, IComparable<ComplexNumber>, IEquatable<ComplexNumber>
{
public ComplexNumber( double real, double imaginary ) { this.real = real;
this.imaginary = imaginary;
}
C H A P T E R 1 3 ■ I N S E A R C H O F C # C A N O N I C A L F O R M S |
387 |
public bool Equals( ComplexNumber other ) { return (this.real == other.real) &&
(this.imaginary == other.imaginary);
}
public override bool Equals( object other ) { bool result = false;
if( other is ComplexNumber ) {
ComplexNumber that = (ComplexNumber) other ;
result = Equals( that );
}
return result;
}
public override int GetHashCode() { return (int) this.Magnitude;
}
public static bool operator ==( ComplexNumber num1, ComplexNumber num2 ) {
return num1.Equals(num2);
}
public static bool operator !=( ComplexNumber num1, ComplexNumber num2 ) {
return !num1.Equals(num2);
}
public int CompareTo( ComplexNumber that ) { int result;
if( Equals(that) ) { result = 0;
}else if( this.Magnitude > that.Magnitude ) { result = 1;
}else {
result = -1;
}
return result;
}
int IComparable.CompareTo( object other ) { if( !(other is ComplexNumber) ) {
throw new ArgumentException( "Bad Comparison!" );
}
return CompareTo( (ComplexNumber) other );
}
public double Magnitude { get {
return Math.Sqrt( Math.Pow(this.real, 2) + Math.Pow(this.imaginary, 2) );
}
}
388 C H A P T E R 1 3 ■ I N S E A R C H O F C # C A N O N I C A L F O R M S
// Other methods removed for clarity
private readonly double real; private readonly double imaginary;
}
public sealed class EntryPoint
{
static void Main()
{
ComplexNumber num1 = new ComplexNumber( 1, 3 ); ComplexNumber num2 = new ComplexNumber( 1, 2 );
int result = num1.CompareTo( num2 );
// Now, try the type-generic version
result = ((IComparable)num1).CompareTo( num2 );
}
}
After the modifications, the first call to CompareTo() in Main() will incur no boxing operations. You’ll also notice that I went one step further and implemented the IComparable.CompareTo method explicitly in order to make it harder to call the typeless version of CompareTo() inadvertently without first explicitly casting the value instance to a reference of type IComparable. For good measure, the Main method demonstrates how to call the typeless version of CompareTo(). Now, the idea is that clients who use the ComplexNumber value can write code in a natural-looking way and get the benefits of better performance. Clients who require going through the interface, such as some container types, can use the IComparable interface, albeit with some boxing. If you’re curious, go ahead and open up the compiled executable with the previous example code inside ILDASM and examine the Main() method. You’ll see that the first call to CompareTo() results in no superfluous boxing, whereas the second call to CompareTo() does, in fact, result in two boxing operations as expected.
As a general rule of thumb, you can apply this idiom to just about any value type’s methods that accept or return a boxed instance of the value type. So far, you’ve seen two such examples of the idiom in use. The first was while implementing Equals() for the ComplexNumber type, and the second was while implementing IComparable.CompareTo().
Summary
This entire chapter can be summarized into a pair of handy checklists that you can use whenever you design a new type in C#. When you design a new class or struct, it’s good design practice to go through the checklist for each type, just as a pilot does before the plane leaves the gate. If you take this approach, you can always feel confident about your designs.
These checklists have been a work in progress for some time. They are, by no means, meant to be complete. You may find the need to augment them or create new entries for new scenarios where you may use classes or structs. These checklists are meant to address the most common scenarios that you’re likely to encounter in a C# design process.
C H A P T E R 1 3 ■ I N S E A R C H O F C # C A N O N I C A L F O R M S |
389 |
Checklist for Reference Types
•Should this class be unsealed? Classes should be declared sealed by default unless they’re clearly intended to be used as a base class. Even then, you should well document how to use them as a base class. Choose sealed classes over unsealed classes.
•Is an object cloneable?
•Implement ICloneable while defaulting to a deep copy: If an object is mutable, default to a deep copy. Otherwise, if immutable, consider a shallow copy as an optimization.
•Avoid use of MemberwiseClone(): Calling MemberwiseClone() creates a new object without calling any constructors. This practice can be dangerous.
•Is an object disposable?
•Implement IDisposable: If you find the need to implement a conventional destructor, use the IDispose pattern instead.
•Implement a finalizer: Disposable objects should implement a finalizer to either catch objects that clients forgot to dispose of or to warn clients that they forgot to do so. Don’t do deterministic destruction work in the C# destructor, which is the finalizer. Only do that kind of work in the Dispose method.
•Suppress finalization during a call to Dispose(): This will make the GC perform much more efficiently. Otherwise, objects live on the heap longer than they need to.
•Should object equivalence checks carry value semantics?
•Override Object.Equals(): Before changing the semantic meaning of Equals(), come up with a solid argument to do so; otherwise, leave the default identity equivalence in place for objects. It is an error to throw exceptions from within your Equals() override.
•Know when to call the base class Equals() implementation: If your object derives from a type whose version of Equals() differs in semantic meaning from your implementation, don’t call the base class version in the override. Otherwise, be sure to do so and include its result with yours.
•Override GetHashCode() too: This is a required step to ensure that you can use objects of this type as a hash code key. If you override Equals(), always override GetHashCode() too.
•Are objects of this type comparable?
•Implement IComparable and override Equals() and GetHashCode(): You’ll want to override these as a group, since they have intertwined implementations.
•Is the object convertible to System.String() or vice versa?
•Override Object.ToString(): The implementation inherited from Object.ToString() merely returns a string name of the object’s type.
•Implement IFormattable if users need finer control over string formatting: Implement the Object.ToString() override by calling IFormattable.ToString() with a format string of G and a null format provider.
•Is an object convertible?
•Override IConvertible so the class will work with System.Convert: In C#, you must implement all methods of the interface. However, for conversion methods that don’t make sense for your class, simply throw an InvalidCastException object.
390C H A P T E R 1 3 ■ I N S E A R C H O F C # C A N O N I C A L F O R M S
•Should this object be immutable?
•Consider making fields read-only and provide only read-only properties: Objects that fundamentally represent a simple value, such as a string or a complex number, are excellent candidates to be immutable objects.
•Do you need to pass this object as a constant immutable method parameter?
•Consider implementing an immutable shim class that contains a reference to a mutable object, which can be passed a method parameter: First, see if it makes sense for your class to be immutable. If so, then there’s no need for this action. If you do need to be able to pass your mutable objects to methods as immutable objects, you can achieve the same effect by using interfaces.
Checklist for Value Types
•Do you desire greater efficiency for your value types?
•Override Equals() and GetHashCode(): The generic version of ValueType.Equals() is not efficient, because it relies upon reflection to do the job. Generally, it’s best to provide a type-safe version of Equals() by implementing IEquatable<T> and then have the typeless version call it. Don’t forget to override GetHashCode() too.
•Provide type-safe overloads of inherited typeless methods and interface methods: For any method that accepts or returns a parameter of type System.Object, provide an overload that uses the concrete value type in its place. That way, clients of the value type can avoid unnecessary boxing. For interfaces, consider hiding the typeless implementation behind explicit interface implementation, if desired.
•Need to modify boxed instances of value?
•Implement an interface to do so: Calling through an interface member implemented by a value type is the only way to change a value type within a boxed instance.
•Are values of this type comparable?
•Implement IComparable, and override Equals() and GetHashCode(): You’ll want to override these as a triplet, since they have intertwined implementations. If you override Equals(), take the previous advice and create a type-safe version as well.
•Is the value convertible to System.String() or vice versa?
•Override ValueType.ToString(): The implementation inherited from ValueType merely returns a string name of the value’s type.
•Implement IFormattable if users need finer control over string formatting: Implement a ValueType.ToString() override that calls IFormattable.ToString() with a format string of G and a null format provider.
•Is value convertible?
•Override IConvertible so struct will work with System.Convert: In C#, all methods of the interface must be implemented. However, for conversion methods that don’t make sense for your struct, simply throw an InvalidCastException object.
•Should this struct be immutable?
•Consider making fields read-only, and provide only read-only properties: Values are excellent candidates to be immutable types.
A P P E N D I X
■ ■ ■
References
The following list of publications are listed as references and/or recommended reading:
Abrams, Brad. .NET Framework Standard Library Annotated Reference,Volumes 1 and 2. Boston, MA: Addison-Wesley Professional, 2004, 2005.
Alexandrescu, Andrei. Modern C++ Design: Generic Programming and Design Patterns Applied. Boston, MA: Addison-Wesley Professional, 2001.
Archer, Tom, and Andrew Whitechapel. Inside C#, Second Edition. Redmond, WA: Microsoft Press, 2002.
Box, Don. Essential COM. Boston, MA: Addison-Wesley Professional, 1997.
Box, Don, with Chris Sells. Essential .NET,Volume 1: The Common Language Runtime. Boston, MA: Addison-Wesley Professional, 2003.
Brown, Keith. The .NET Developer’s Guide to Windows Security. Boston, MA: Addison-Wesley Professional, 2004.
Coplien, James O. Advanced C++ Programming Styles and Idioms. Boston, MA: Addison-Wesley Professional, 1991.
Cwalina, Krzysztof, and Brad Abrams. Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries. Boston, MA: Addison-Wesley Professional, 2005.
Ecma International. Standard ECMA-334: C# Language Specification, Third Edition. June 2005.
Ecma International. Standard ECMA-335: Common Language Infrastructure (CLI), Third Edition. June 2005.
Ecma International. Standard ECMA-372: C++/CLI Language Specification. December 2005.
Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Boston, MA: Addison-Wesley Professional, 1995.
Hejlsberg, Anders, Scott Wiltamuth, and Peter Golde. The C# Programming Language. Boston, MA: Addison-Wesley Professional, 2003.
Kaplan, Michael, and Cathy Wissink. “Custom Cultures: Extend Your Code’s Global Reach with New Features in the .NET Framework 2.0,” MSDN Magazine, October 2005.
LaMacchia, Brian A., Sebastian Lange, Matthew Lyons, Rudi Martin, and Kevin T. Price. .NET Framework Security. Upper Saddle River, NJ: Pearson Education, 2002.
Meyers, Scott. Effective C++, Second Edition: 50 Specific Ways to Improve Your Programs and Designs. Boston, MA: Addison-Wesley Professional, 1997.
Meyers, Scott. More Effective C++: 35 New Ways to Improve Your Programs and Designs. Boston, MA: Addison-Wesley Professional, 1995.
391
392A P P E N D I X ■ R E F E R E N C E S
Miller, Jim, and Susann Ragsdale. The Common Language Infrastructure Annotated Standard. Boston, MA: Addison-Wesley Professional, 2003.
Nathan, Adam. .NET and COM: The Complete Interoperability Guide. Indianapolis, IN: Sams, 2002. Richter, Jeffrey. Applied Microsoft .NET Framework Programming. Redmond, WA: Microsoft Press, 2002.
Robbins, John. “Unhandled Exceptions and Tracing in the .NET Framework 2.0,” MSDN Magazine, July 2005.
Russinovich, Mark E., and David A. Solomon. Microsoft Windows Internals, Fourth Edition: Microsoft Windows Server 2003,Windows XP, and Windows 2000. Redmond, WA: Microsoft Press, 2004.
Schmidt, Douglas C. “Monitor Object: An Object Behavioral Pattern for Concurrent Programming,” Department of Computer Science and Engineering, Washington University, St. Louis, MO, April 2005.
Stroustrup, Bjarne. The Design and Evolution of C++. Boston, MA: Addison-Wesley Professional, 1994.
Sutter, Herb. Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Exception-Safety Solutions. Boston, MA: Addison-Wesley Professional, 1999.
Sutter, Herb. Exception C++ Style: 40 New Engineering Puzzles, Programming Problems, and Solutions. Boston, MA: Addison-Wesley Professional, 2004.
Sutter, Herb. More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions. Boston, MA: Addison-Wesley Professional, 2001.
Toub, Stephen. “High Availability: Keep Your Code Running with the Reliability Features of the .NET Framework,” MSDN Magazine, October 2005.
Troelsen, Andrew. Pro C# 2005 and the .NET 2.0 Platform. Berkeley, CA: Apress, 2005. Vermeulen, Allan. “An Asynchronous Design Pattern,” Dr. Dobb’s Journal, June 1996.
Blogs
http://blogs.msdn.com/brada/
http://blogs.msdn.com/cbrumme/
http://blogs.msdn.com/kcwalina/
http://blogs.msdn.com/maoni/
http://blogs.msdn.com/ricom/
http://pluralsight.com/blogs/dbox/
http://pluralsight.com/blogs/hsutter/
http://www.sellsbrothers.com/news/
Index
■Symbols
@ character, preceding verbatim strings, 179 {} curly braces, 245, 261
-- prefix operator, 135 -= operator, 242
+ addition operator, 135, 191 ++ postfix operator, 135
+= operator, 242 ; semicolon, 17
■A
A.InitZ() method, 36 Abort() method, 300–302
AbortRequested state, of threads, 300 abstract classes, 56
abstract keyword, 56, 98 abstract methods, 98 access modifiers, 35, 46
interfaces and, 109 accessibility, 46, 49
accessors, properties and, 40, 42 AcquireReaderLock() method, 324 AcquireWriterLock() method, 324 Active Template Library (ATL), 273 Add() method, 250, 265, 281 Adder property, 247
addition operator (+) composite strings and, 191 overloading, 135
Add<int>, 267
Add<T>() method, 265, 281
allocated resources, working with, 169–173 AllocateDataSlot() method, 306 AllocateNamedDataSlot() method, 306 anonymous methods, 5, 243–253
captured variables and, 247–249 AOP (aspect-oriented programming), 21
AOSD (aspect-oriented software development), 21 ApartmentState property, 308 AppDomain.UnhandledException, 149 AppDomainUnloadException, 149
Append() method, 191 AppendFormat() method, 186, 191 ApplicationException, 169 applications, 1, 8
ApplyRaiseOf() method, 238 ArgumentException, 212 ArgumentOutOfRangeException, 148, 151, 158,
191
static constructors and, 156
Array class, 258 ArrayList, 259 arrays, 203–210
covariance and, 204 declaring, 203 jagged, 209
multidimensional, 207–210 rectangular, 207
sortability and, 205 vectors and, 206
as operator, 24
aspect-oriented programming (AOP), 21 aspect-oriented software development (AOSD),
21 assemblies, 9–11
naming, 10 assembly loader, 9–11
asynchronous method calls, 298, 330 ATL (Active Template Library), 273 attributes, 35
AutoResetEvent, 326
■B
background threads, 304 base classes, 33
NVI pattern and, 341–343 base keyword, 38, 54, 84 beforefieldinit attribute, 83 BeginInvoke() method, 330–333
BeginMethod()/EndMethod() asynchronous programming pattern, 298
bidirectional iterators, 226–230 BidirectionalIterator class, 227 binary operators, 137
parameters and, 134 BinarySearch() method, 205 Bind2nd class, 250
Binder property, 250 bool type, 18
boolean operators, 142 Boost Library, 250 BoundDelegate, 253 boxing conversion, 24
boxing/unboxing, 71–77, 217
explicit interface implementation and, 124 Monitor class and, 318
value type interface implementation and, 118, 385
break statement, 31 Bridge pattern, 239
393
