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

Addison Wesley Essential C Sharp_4 pot

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 (1.81 MB, 98 trang )

ptg
Boxing 345
The result is that no copy back from the heap to the stack occurs. Instead,
the modified heap data is ready for garbage collection while the data in
angle remains unmodified.
In the last case, the cast to IAngle occurs with the data on the heap
already, so no copy occurs. MoveTo() updates the _Hours value and the
code behaves as desired.
ADVANCED TOPIC
Unboxing Avoided
As discussed earlier, the unboxing instruction does not include the copy
back to the stack. Although some languages support the ability to access
value types on the heap directly, this is possible in C# only when the value
type is accessed as a field on a reference type. Since interfaces are reference
types, unboxing and copying can be avoided, when accessing the boxed
value via its interface.
When you call an interface method on a value type, the instance must
be a variable because the method might mutate the value. Since unboxing
produces a managed address, the runtime has a storage location and hence
a variable. As a result, the runtime simply passes that managed address on
an interface and no unboxing operation is necessary.
Listing 8.7 added an interface implementation to the
Angle struct. List-
ing 8.8 uses the interface to avoid unboxing.
Listing 8.8: Avoiding Unboxing and Copying
int number;
object thing;
number = 42;
// Boxing
thing = number;
string text = ((IFormattable)thing).ToString(


"X", null);
Console.WriteLine(text);
Interfaces are reference types anyway, so calling an interface member
does not even require unboxing. Furthermore, calling a struct’s
ToString() method (that overrides object’s ToString() method) does not
// No unbox instruction.
From the Library of Wow! eBook
ptg
Chapter 8: Value Types346
require an unbox. When compiling, it is clear that a struct’s overriding
ToString() method will always be called because all value types are
sealed. The result is that the C# compiler can instruct a direct call to the
method without unboxing.
Enums
Compare the two code snippets shown in Listing 8.9.
Listing 8.9: Comparing an Integer Switch to an Enum Switch
int connectionState;
//
switch (connectionState)
{
case 0:
//
break;
case 1:
//
break;
case 2:
//
break;
case 3:

//
break;
}
ConnectionState connectionState;
//
switch (connectionState)
{
case ConnectionState.Connected:
//
break;
case ConnectionState.Connecting:
//
break;
case ConnectionState.Disconnected:
//
break;
case ConnectionState.Disconnecting:
//
break;
}
From the Library of Wow! eBook
ptg
Enums 347
Obviously, the difference in terms of readability is tremendous because in
the second snippet, the cases are self-documenting to some degree. How-
ever, the performance at runtime is identical. To achieve this, the second
snippet uses enum values in each case statement.
An enum is a type that the developer can define. The key characteristic
of an enum is that it identifies a compile-time-defined set of possible val-
ues, each value referred to by name, making the code easier to read. You

define an enum using a style similar to that for a class, as Listing 8.10
shows.
Listing 8.10: Defining an Enum
enum ConnectionState
{
Disconnected,
Connecting,
Connected,
Disconnecting
}
You refer to an enum value by prefixing it with the enum name; to refer to
the Connected value, for example, you use ConnectionState.Connected. You
should not use the enum names within the enum value name, to avoid the
redundancy of something such as ConnectionState.ConnectionStateCon-
nected. By convention, the enum name itself should be singular, unless the
enums are bit flags (discussed shortly).
By default, the first enum value is 0 (technically, it is 0 implicitly con-
verted to the underlying enum type), and each subsequent entry increases
by one. However, you can assign explicit values to enums, as shown in
Listing 8.11.
NOTE
An enum is helpful even for Boolean parameters. For example, a
method call such as SetState(true) is less readable than SetState
(DeviceState.On).
From the Library of Wow! eBook
ptg
Chapter 8: Value Types348
Listing 8.11: Defining an Enum Type
enum ConnectionState : short
{

Disconnected,
Connecting = 10,
Connected,
Joined = Connected,
Disconnecting
}
Disconnected has a default value of 0, Connecting has been explicitly
assigned 10, and consequently, Connected will be assigned 11. Joined is
assigned 11, the value referred to by Connected. (In this case, you do not
need to prefix Connected with the enum name, since it appears within its
scope.) Disconnecting is 12.
An enum always has an underlying type, which may be int, uint, long,
or ulong, but not char. In fact, the enum type’s performance is equivalent
to that of the underlying type. By default, the underlying value type is int,
but you can specify a different type using inheritance type syntax. Instead
of int, for example, Listing 8.11 uses a short. For consistency, the syntax
emulates that of inheritance, but this doesn’t actually make an inheritance
relationship. The base class for all enums is System.Enum. Furthermore,
these classes are sealed; you can’t derive from an existing enum type to
add additional members.
Successful conversion doesn’t work just for valid enum values. It is pos-
sible to cast 42 into a ConnectionState, even though there is no corre-
sponding ConnectionState enum value. If the value successfully converts
to the underlying type, the conversion will be successful.
The advantage to allowing casting, even without a corresponding enum
value, is that enums can have new values added in later API releases, with-
out breaking earlier versions. Additionally, the enum values provide names
for the known values while still allowing unknown values to be assigned at
runtime. The burden is that developers must code defensively for the possi-
bility of unnamed values. It would be unwise, for example, to replace

case
ConnectionState.Disconnecting with default and expect that the only pos-
sible value for the default case was ConnectionState.Disconnecting.
Instead, you should handle the Disconnecting case explicitly and the
Default case should report an error or behave innocuously. As indicated
From the Library of Wow! eBook
ptg
Enums 349
before, however, conversion between the enum and the underlying type, and
vice versa, involves an explicit cast, not an implicit conversion. For example,
code cannot call ReportState(10) where the signature is void Report-
State(ConnectionState state). (The only exception is passing 0 because
there is an implicit conversion from 0 to any enum.) The compiler will per-
form a type check and require an explicit cast if the type is not identical.
Although you can add additional values to an enum in a later version of
your code, you should do this with care. Inserting an enum value in the
middle of an enum will bump the values of all later enums (adding
Flooded or Locked before Connected will change the Connected value, for
example). This will affect the versions of all code that is recompiled against
the new version. However, any code compiled against the old version will
continue to use the old values, making the intended values entirely differ-
ent. Besides inserting an enum value at the end of the list, one way to avoid
changing enum values is to assign values explicitly.
Enums are slightly different from other value types because enums
derive from
System.Enum before deriving from System.ValueType.
Type Compatibility between Enums
C# also does not support a direct cast between arrays of two different
enums. However, there is a way to coerce the conversion by casting first to
an array and then to the second enum. The requirement is that both enums

share the same underlying type, and the trick is to cast first to Sys-
tem.Array, as shown at the end of Listing 8.12.
Listing 8.12: Casting between Arrays of Enums
enum ConnectionState1
{
Disconnected,
Connecting,
Connected,
Disconnecting
}
enum ConnectionState2
{
Disconnected,
Connecting,
From the Library of Wow! eBook
ptg
Chapter 8: Value Types350
Connected,
Disconnecting
}
class Program
{
static void Main()
{
ConnectionState1[] states =
}
}
This exploits the fact that the CLR’s notion of assignment compatibility is
more lenient than C#’s. (The same trick is possible for illegal conversions,
such as int[] to uint[].) However, use this approach cautiously because

there is no C# specification detailing that this should work across different
CLR implementations.
Converting between Enums and Strings
One of the conveniences associated with enums is the fact that the
ToString() method, which is called by methods such as System.Con-
sole.WriteLine(), writes out the enum value identifier:
System.Diagnostics.Trace.WriteLine(string.Format(
"The Connection is currently {0}.",
ConnectionState.Disconnecting));
The preceding code will write the text in Output 8.3 to the trace buffer.
Conversion from a string to an enum is a little harder to find because it
involves a static method on the System.Enum base class. Listing 8.13 pro-
vides an example of how to do it without generics (see Chapter 11), and
Output 8.4 shows the results.
(ConnectionState1[])(Array)new ConnectionState2[42];
OUTPUT 8.3:
The Connection is currently Disconnecting.
From the Library of Wow! eBook
ptg
Enums 351
Listing 8.13: Converting a String to an Enum Using Enum.Parse()
ThreadPriorityLevel priority = (ThreadPriorityLevel)Enum.Parse(
typeof(ThreadPriorityLevel), "Idle");
Console.WriteLine(priority);
The first parameter to Enum.Parse() is the type, which you specify using
the keyword typeof(). This is a compile-time way of identifying the type,
like a literal for the type value (see Chapter 17).
Until .NET Framework 4, there was no TryParse() method, so code prior
to then should include appropriate exception handling if there is a chance
the string will not correspond to an enum value identifier. .NET Framework

4’s
TryParse<T>() method uses generics, but the type parameters can be
implied, resulting in the to-enum conversion example shown in Listing 8.14.
Listing 8.14: Converting a String to an Enum Using Enum.TryParse<T>()
System.Threading.ThreadPriorityLevel priority;
if(Enum.TryParse("Idle", out priority))
{
Console.WriteLine(priority);
}
This conversion offers the advantage that there is no need to use exception
handling if the string doesn’t convert. Instead, code can check the Boolean
result returned from the call to TryParse<T>().
Regardless of whether code uses the “Parse” or “TryParse” approach,
the key caution about converting from a string to an enum is that such a
cast is not localizable. Therefore, developers should use this type of cast
only for messages that are not exposed to users (assuming localization is a
requirement).
Enums as Flags
Many times, developers not only want enum values to be unique, but they
also want to be able to combine them to represent a combinatorial value.
OUTPUT 8.4:
Idle
From the Library of Wow! eBook
ptg
Chapter 8: Value Types352
For example, consider System.IO.FileAttributes. This enum, shown in
Listing 8.15, indicates various attributes on a file: read-only, hidden,
archive, and so on. The difference is that unlike the ConnectionState attri-
bute, where each enum value was mutually exclusive, the FileAttributes
enum values can and are intended for combination: A file can be both

read-only and hidden. To support this, each enum value is a unique bit (or
a value that represents a particular combination).
Listing 8.15: Using Enums As Flags
public enum FileAttributes
{
ReadOnly = 1<<0, // 000000000000001
Hidden = 1<<1, // 000000000000010
System = 1<<2, // 000000000000100
Directory = 1<<4, // 000000000010000
Archive = 1<<5, // 000000000100000
Device = 1<<6, // 000000001000000
Normal = 1<<7, // 000000010000000
Temporary = 1<<8, // 000000100000000
SparseFile = 1<<9, // 000001000000000
ReparsePoint = 1<<10, // 000010000000000
Compressed = 1<<11, // 000100000000000
Offline = 1<<12, // 001000000000000
NotContentIndexed = 1<<13, // 010000000000000
Encrypted = 1<<14, // 100000000000000
}
Because enums support combined values, the guideline for the enum
name of bit flags is plural.
To join enum values you use a bitwise OR operator, as shown in
Listing 8.16.
Listing 8.16: Using Bitwise OR and AND with Flag Enums
using System;
using System.IO;
public class Program
{
public static void Main()

{
//
string fileName = @"enumtest.txt";
From the Library of Wow! eBook
ptg
Enums 353
System.IO.FileInfo file =
new System.IO.FileInfo(fileName);
file.Attributes = FileAttributes.Hidden |
FileAttributes.ReadOnly;
Console.WriteLine("{0} | {1} = {2}",
FileAttributes.Hidden, FileAttributes.ReadOnly,
(int)file.Attributes);
if ( (file.Attributes & FileAttributes.Hidden) !=
FileAttributes.Hidden)
{
throw new Exception("File is not hidden.");
}
if (( file.Attributes & FileAttributes.ReadOnly) !=
FileAttributes.ReadOnly)
{
throw new Exception("File is not read-only.");
}
//
}
The results of Listing 8.16 appear in Output 8.5.
Using the bitwise OR operator allows you to set the file to both read-only
and hidden. In addition, you can check for specific settings using the bit-
wise AND operator.
Each value within the enum does not need to correspond to only one

flag. It is perfectly reasonable to define additional flags that correspond to
frequent combinations of values. Listing 8.17 shows an example.
Listing 8.17: Defining Enum Values for Frequent Combinations
enum DistributedChannel
{
None = 0,
Transacted = 1,
Queued = 2,
OUTPUT 8.5:
Hidden | ReadOnly = 3
From the Library of Wow! eBook
ptg
Chapter 8: Value Types354
Encrypted = 4,
Persisted = 16,
}
Furthermore, flags such as None are appropriate if there is the possibility
that none is a valid value. In contrast, avoid enum values corresponding to
things such as Maximum as the last enum, because Maximum could be inter-
preted as a valid enum value. To check whether a value is included within
an enum use the System.Enum.IsDefined() method.
ADVANCED TOPIC
FlagsAttribute
If you decide to use flag-type values, the enum should include FlagsAt-
tribute. The attribute appears in square brackets (see Chapter 17), just
prior to the enum declaration, as shown in Listing 8.18.
Listing 8.18: Using FlagsAttribute
// FileAttributes defined in System.IO.
public enum FileAttributes
{

ReadOnly = 1<<0, // 000000000000001
Hidden = 1<<1, // 000000000000010
//
}
using System;
using System.Diagnostics;
using System.IO;
class Program
{
public static void Main()
{
string fileName = @"enumtest.txt";
FileInfo file = new FileInfo(fileName);
file.Open(FileMode.Create).Close();
FileAttributes startingAttributes =
file.Attributes;
FaultTolerant =
Transacted | Queued | Persisted
[Flags] // Decorating an enum with FlagsAttribute.
From the Library of Wow! eBook
ptg
Enums 355
file.Attributes = FileAttributes.Hidden |
FileAttributes.ReadOnly;
Console.WriteLine("\"{0}\" outputs as \"{1}\"",
file.Attributes.ToString().Replace(",", " |"),
file.Attributes);
FileAttributes attributes =
(FileAttributes) Enum.Parse(typeof(FileAttributes),
file.Attributes.ToString());

Console.WriteLine(attributes);
File.SetAttributes(fileName,
startingAttributes);
file.Delete();
}
}
The results of Listing 8.18 appear in Output 8.6.
The flag documents that the enum values can be combined. Furthermore,
it changes the behavior of the ToString() and Parse() methods. For exam-
ple, calling ToString() on an enum that is decorated with FlagsAttribute
writes out the strings for each enum flag that is set. In Listing 8.18,
file.Attributes.ToString() returns ReadOnly, Hidden rather than the 3
it would have returned without the FileAttributes flag. If two enum val-
ues are the same, the ToString() call would return the first value. As men-
tioned earlier, however, you should use this with caution because it is not
localizable.
Parsing a value from a string to the enum also works. Each enum value
identifier is separated by a comma.
It is important to note that FlagsAttribute does not automatically
assign unique flag values or check that they have unique values. Doing this
wouldn’t make sense, since duplicates and combinations are often desir-
able. Instead, you must assign the values of each enum item explicitly.
OUTPUT 8.6:
"ReadOnly | Hidden" outputs as "ReadOnly, Hidden"
ReadOnly, Hidden
From the Library of Wow! eBook
ptg
Chapter 8: Value Types356
SUMMARY
This chapter began with a discussion of how to define custom value types.

One of the key guidelines that emerge is to create immutable value types.
Boxing also was part of the value type discussion.
The idiosyncrasies introduced by boxing are subtle, and the vast major-
ity of them lead to issues at execution time rather than at compile time.
Although it is important to know about these in order to try to avoid them,
in many ways, focused attention on the potential pitfalls overshadows the
usefulness and performance advantages of value types. Programmers
should not be overly concerned about using value types. Value types per-
meate virtually every chapter of this book, and yet the idiosyncrasies do
not. I have staged the code surrounding each issue to demonstrate the con-
cern, but in reality, these types of patterns rarely occur. The key to avoid-
ing most of them is to follow the guideline of not creating mutable value
types; this is why you don’t encounter them within the primitive types.
Perhaps the only issue to occur with some frequency is repetitive box-
ing operations within loops. However, C# 2.0 greatly reduces the chance of
this with the addition of generics, and even without that, performance is
rarely affected enough to warrant avoidance until a particular algorithm
with boxing is identified as a bottleneck.
Furthermore, custom structs (value types) are relatively rare. They
obviously play an important role within C# development, but when com-
pared to the number of classes, custom structs are rare—when custom
structs are required, it is generally in frameworks targeted at interoperat-
ing with managed code or a particular problem space.
In addition to demonstrating structs, this chapter introduced enums.
This is a standard construct available in most programming languages,
and it deserves prominent consideration if you want to improve API
usability and code readability.
The next chapter highlights more guidelines to creating well-formed
types, both structs and otherwise. It begins by looking at overriding the
virtual members of objects and defining operator-overloading methods.

These two topics apply to both structs and classes, but they are somewhat
more critical in completing a struct definition and making it well formed.
From the Library of Wow! eBook
ptg
357
9
Well-Formed Types
HE PREVIOUS CHAPTERS covered most of the constructs for defining
classes and structs. However, several details remain concerning
rounding out the type definition with fit-and-finish-type functionality.
This chapter introduces how to put the final touches on a type declaration.
Overriding object Members
Chapter 6 discussed how all types derive from object. In addition, it
reviewed each method available on object and discussed how some of
T
3
2
45
6
7
1
Well-Formed
Types
Overriding object
Members
Operator
Overloading
Referencing Other
Assemblies
Defining

Namespaces
XML
Comments
Associating XML Comments
with Programming Constructs
Generating an XML
Documentation File
Garbage
Collection
Weak References
Resource
Cleanup
Finalizers
Deterministic Finalization
with the using Statement
Garbage Collection
and Finalization
Resource Utilization and
Finalization Guidelines
From the Library of Wow! eBook
ptg
Chapter 9: Well-Formed Types358
them are virtual. This section discusses the details concerning overloading
the virtual methods.
Overriding ToString()
By default, calling ToString() on any object will return the fully qualified
name of the class. Calling ToString() on a System.IO.FileStream object
will return the string System.IO.FileStream, for example. For some
classes, however, ToString() can be more meaningful. On string, for
example, ToString() returns the string value itself. Similarly, returning a

Contact’s name would make more sense. Listing 9.1 overrides ToString()
to return a string representation of Coordinate.
Listing 9.1: Overriding ToString()
public struct Coordinate
{
public Coordinate(Longitude longitude, Latitude latitude)
{
_Longitude = longitude;
_Latitude = latitude;
}
public Longitude Longitude { get { return _Longitude; } }
private readonly Longitude _Longitude;
public Latitude Latitude { get { return _Latitude; } }
private readonly Latitude _Latitude;

//
}
Write methods such as Console.WriteLine() call an object’s ToString()
method, so overloading it often outputs more meaningful information
than the default implementation.
Overriding GetHashCode()
Overriding GetHashCode() is more complex than overriding ToString().
Regardless, you should override GetHashCode() when you are overriding
Equals(), and there is a compiler warning to indicate this. Overriding
public override string ToString()
{
return string.Format("{0} {1}", Longitude, Latitude);
}
From the Library of Wow! eBook
ptg

Overriding object Members 359
GetHashCode() is also a good practice when you are using it as a key into a
hash table collection (System.Collections.Hashtable and System.Col-
lections.Generic.Dictionary, for example).
The purpose of the hash code is to efficiently balance a hash table by gener-
ating a number that corresponds to the value of an object. Here are some
implementation principles for a good GetHashCode() implementation.
• Required: Equal objects must have equal hash codes (if a.Equals(b),
then a.GetHashCode() == b.GetHashCode()).
• Required: GetHashCode()’s returns over the life of a particular object
should be constant (the same value), even if the object’s data
changes. In many cases, you should cache the method return to
enforce this.
• Required: GetHashCode() should not throw any exceptions; GetHash-
Code() must always successfully return a value.
• Performance: Hash codes should be unique whenever possible. How-
ever, since hash code returns only an int, there has to be an overlap in
hash codes for objects that have potentially more values than an int
can hold—virtually all types. (An obvious example is long, since
there are more possible long values than an int could uniquely
identify.)
• Performance: The possible hash code values should be distributed
evenly over the range of an int. For example, creating a hash that
doesn’t consider the fact that distribution of a string in Latin-based
languages primarily centers on the initial 128 ASCII characters would
result in a very uneven distribution of string values and would not be
a strong GetHashCode() algorithm.
• Performance: GetHashCode() should be optimized for performance.
GetHashCode() is generally used in Equals() implementations to
short-circuit a full equals comparison if the hash codes are different.

As a result, it is frequently called when the type is used as a key type
in dictionary collections.
• Performance: Small differences between two objects should result in
large differences between hash code values—ideally, a 1-bit differ-
ence in the object results in around 16 bits of the hash code changing,
From the Library of Wow! eBook
ptg
Chapter 9: Well-Formed Types360
on average. This helps ensure that the hash table remains balanced no
matter how it is “bucketing” the hash values.
• Security: It should be difficult for an attacker to craft an object that has
a particular hash code. The attack is to flood a hash table with large
amounts of data that all hash to the same value. The hash table imple-
mentation then becomes O(n) instead of O(1), resulting in a possible
denial-of-service attack.
Consider the GetHashCode() implementation for the Coordinate type
shown in Listing 9.2.
Listing 9.2: Implementing GetHashCode()
public struct Coordinate
{
public Coordinate(Longitude longitude, Latitude latitude)
{
_Longitude = longitude;
_Latitude = latitude;
}
public Longitude Longitude { get { return _Longitude; } }
private readonly Longitude _Longitude;
public Latitude Latitude { get { return _Latitude; } }
private readonly Latitude _Latitude;


//
}
Generally, the key is to use the XOR operator over the hash codes from the
relevant types, and to make sure the XOR operands are not likely to be
public override int GetHashCode()
{
int hashCode = Longitude.GetHashCode();
// As long as the hash codes are not equal
if(Longitude.GetHashCode() != Latitude.GetHashCode())
{
hashCode ^= Latitude.GetHashCode(); // eXclusive OR
}
return hashCode;
}
From the Library of Wow! eBook
ptg
Overriding object Members 361
close or equal—or else the result will be all zeroes. (In those cases where
the operands are close or equal, consider using bitshifts and adds instead.)
The alternative operands, AND and OR, have similar restrictions, but the
restrictions occur more frequently. Applying AND multiple times tends
toward all 0 bits, and applying OR tends toward all 1 bits.
For finer-grained control, split larger-than-int types using the shift
operator. For example, GetHashCode() for a long called value is imple-
mented as follows:
int GetHashCode() { return ((int)value ^ (int)(value >> 32)) };
Also, note that if the base class is not object, then base.GetHashCode()
should be included in the XOR assignment.
Finally, Coordinate does not cache the value of the hash code. Since
each field in the hash code calculation is readonly, the value can’t change.

However, implementations should cache the hash code if calculated val-
ues could change or if a cached value could offer a significant performance
advantage.
Overriding Equals()
Overriding Equals() without overriding GetHashCode() results in a warn-
ing such as that shown in Output 9.1.
Generally, programmers expect overriding Equals() to be trivial, but it
includes a surprising number of subtleties that require careful thought and
testing.
Object Identity versus Equal Object Values
Two references are identical if both refer to the same instance. object, and
therefore, all objects, include a static method called ReferenceEquals()
that explicitly checks for this object identity (see Figure 9.1)
OUTPUT 9.1:
warning CS0659: ’<Class Name>’ overrides Object.Equals(object o) but
does not override Object.GetHashCode()
From the Library of Wow! eBook
ptg
Chapter 9: Well-Formed Types362
However, identical reference is not the only type of equality. Two object
instances can also be equal if the values that identify them are equal. Con-
sider the comparison of two ProductSerialNumbers shown in Listing 9.3.
Listing 9.3: Equal
public sealed class ProductSerialNumber
{
// See Appendix B
}
Figure 9.1: Identity
Equal Value Types
Equal Reference Types

Identical (Equal References)
Heap
Stack
00 66 00 20 00
00 66 00 72 00
6F 00 6D 00 20
9C 11 C9 78 00
00 00 00 34 12
A6 00 00 00 00
00 33 00 00 00
00 00 00 00 00
00 00 00 00 00
00 00 00 00 00
D4 4C C7 78 02
41 00 20 00 63
00 61 00 63 00
6F 00 70 00 68
00 6F 00 6E 00
79 00 20 00 6F
00 66 00 20 00
72 00 61 00 6D
D4 4C C7 78 02
42
42
0x00A60289
0x00A64799
0x00A61234
0x00A61234
From the Library of Wow! eBook
ptg

Overriding object Members 363
class Program
{
static void Main()
{
ProductSerialNumber serialNumber1 =
new ProductSerialNumber("PV", 1000, 09187234);
ProductSerialNumber serialNumber2 = serialNumber1;
ProductSerialNumber serialNumber3 =
new ProductSerialNumber("PV", 1000, 09187234);
// These serial numbers ARE the same object identity.
if(!ProductSerialNumber.ReferenceEquals(serialNumber1,
serialNumber2))
{
throw new Exception(
"serialNumber1 does NOT " +
"reference equal serialNumber2");
}
// and, therefore, they are equal
else if(!serialNumber1.Equals(serialNumber2))
{
throw new Exception(
"serialNumber1 does NOT equal serialNumber2");
}
else
{
Console.WriteLine(
"serialNumber1 reference equals serialNumber2");
Console.WriteLine(
"serialNumber1 equals serialNumber2");

}
// These serial numbers are NOT the same object identity.
if (ProductSerialNumber.ReferenceEquals(serialNumber1,
serialNumber3))
{
throw new Exception(
"serialNumber1 DOES reference " +
"equal serialNumber3");
}
// but they are equal (assuming Equals is overloaded).
else if(!serialNumber1.Equals(serialNumber3) ||
serialNumber1 != serialNumber3)
{
throw new Exception(
"serialNumber1 does NOT equal serialNumber3");
}
From the Library of Wow! eBook
ptg
Chapter 9: Well-Formed Types364
Console.WriteLine( "serialNumber1 equals serialNumber3" );
Console.WriteLine( "serialNumber1 == serialNumber3" );
}
}
The results of Listing 9.3 appear in Output 9.2.
OUTPUT 9.2:
As the last assertion demonstrates with ReferenceEquals(), serial-
Number1 and serialNumber3 are not the same reference. However, the code
constructs them with the same values and both logically associate with the
same physical product. If one instance was created from data in the database
and another was created from manually entered data, you would expect the

instances would be equal and, therefore, that the product would not be
duplicated (reentered) in the database. Two identical references are obvi-
ously equal; however, two different objects could be equal but not reference
equal. Such objects will not have identical object identities, but they may
have key data that identifies them as being equal objects.
Only reference types can be reference equal, thereby supporting the
concept of identity. Calling
ReferenceEquals() on value types will always
return false since, by definition, the value type directly contains its data,
not a reference. Even when ReferenceEquals() passes the same variable in
both (value type) parameters to ReferenceEquals(), the result will still be
false because the very nature of value types is that they are copied into
the parameters of the called method. Listing 9.4 demonstrates this behav-
ior. In other words, ReferenceEquals() boxes the value types. Since each
argument is put into a “different box” (location on the stack), they are
never reference equal.
Listing 9.4: Value Types Do Not Even Reference Equal Themselves
public struct Coordinate
{
public Coordinate(Longitude longitude, Latitude latitude)
{
serialNumber1 reference equals serialNumber2
serialNumber1 equals serialNumber3
serialNumber1 == serialNumber3
From the Library of Wow! eBook
ptg
Overriding object Members 365
_Longitude = longitude;
_Latitude = latitude;
}

public Longitude Longitude { get { return _Longitude; } }
private readonly Longitude _Longitude;
public Latitude Latitude { get { return _Latitude; } }
private readonly Latitude _Latitude;
//
}
class Program
{
public void Main()
{
//
Coordinate coordinate1 =
new Coordinate( new Longitude(48, 52),
new Latitude(-2, -20));
// Value types will never be reference equal.
if ( Coordinate.ReferenceEquals(coordinate1,
coordinate1) )
{
throw new Exception(
"coordinate1 reference equals coordinate1");
}
Console.WriteLine(
"coordinate1 does NOT reference equal itself" );
}
}
In contrast to the definition of Coordinate as a reference type in
Chapter 8, the definition going forward is that of a value type (struct)
because the combination of Longitude and Latitude data is logically
thought of as a value and the size is less than 16 bytes. (In Chapter 8, Coor-
dinate aggregated Angle rather than Longitude and Latitude.) A contrib-

uting factor to declaring Coordinate as a value type is that it is a (complex)
numeric value that has particular operations on it. In contrast, a reference
type such as Employee is not a value that you manipulate numerically, but
rather refers to an object in real life.
From the Library of Wow! eBook
ptg
Chapter 9: Well-Formed Types366
Implementing Equals()
To determine whether two objects are equal (they have same identifying
data), you use an object’s Equals() method. The implementation of this
virtual method on object uses ReferenceEquals() to evaluate equality.
Since this implementation is often inadequate, it is necessary to sometimes
override Equals() with a more appropriate implementation.
For objects to equal each other, the expectation is that the identifying
data within them is equal. For ProductSerialNumbers, for example, the
ProductSeries, Model, and Id must be the same; however, for an Employee
object, perhaps comparing EmployeeIds would be sufficient for equality.
To correct object.Equals() implementation, it is necessary to override it.
Value types, for example, override the Equals() implementation to
instead use the fields that the type includes.
The steps for overriding Equals() are as follows.
1. Check for null.
2. Check for reference equality if the type is a reference type.
3. Check for equivalent types.
4. Invoke a typed helper method that can treat the operand as the com-
pared type rather than an object (see the Equals(Coordinate obj)
method in Listing 9.5).
5. Possibly check for equivalent hash codes to short-circuit an extensive,
field-by-field comparison. (Two objects that are equal cannot have
different hash codes.)

6. Check base.Equals() if the base class overrides Equals().
7. Compare each identifying field for equality.
8. Override GetHashCode().
9. Override the == and != operators (see the next section).
Listing 9.5 shows a sample Equals() implementation.
Listing 9.5: Overriding Equals()
public struct Longitude
{
//
}
From the Library of Wow! eBook
ptg
Overriding object Members 367
public struct Latitude
{
//
}
public struct Coordinate
{
public Coordinate(Longitude longitude, Latitude latitude)
{
_Longitude = longitude;
_Latitude = latitude;
}
public Longitude Longitude { get { return _Longitude; } }
private readonly Longitude _Longitude;
public Latitude Latitude { get { return _Latitude; } }
private readonly Latitude _Latitude;
public override bool Equals(object obj)
{

// STEP 1: Check for null
if (obj == null)
{
return false;
}
// STEP 3: equivalent data types
if (this.GetType() != obj.GetType())
{
return false;
}
return Equals((Coordinate)obj);
}
public bool Equals(Coordinate obj)
{
// STEP 1: Check for null if a reference type
// (e.g., a reference type)
// if (obj == null)
// {
// return false;
// }
// STEP 2: Check for ReferenceEquals if this
// is a reference type
// if ( ReferenceEquals(this, obj))
// {
// return true;
// }
From the Library of Wow! eBook
ptg
Chapter 9: Well-Formed Types368
// STEP 4: Possibly check for equivalent hash codes

// if (this.GetHashCode() != obj.GetHashCode())
// {
// return false;
// }
// STEP 5: Check base.Equals if base overrides Equals()
// System.Diagnostics.Debug.Assert(
// base.GetType() != typeof(object) );
// if ( !base.Equals(obj) )
// {
// return false;
// }
// STEP 6: Compare identifying fields for equality
// using an overload of Equals on Longitude.
return ( (Longitude.Equals(obj.Longitude)) &&
(Latitude.Equals(obj.Latitude)) );
}
// STEP 7: Override GetHashCode.
public override int GetHashCode()
{
int hashCode = Longitude.GetHashCode();
hashCode ^= Latitude.GetHashCode(); // Xor (eXclusive OR)
return hashCode;
}
}
In this implementation, the first two checks are relatively obvious. Checks
4–6 occur in an overload of Equals() that takes the Coordinate data type
specifically. This way, a comparison of two Coordinates will avoid
Equals(object obj) and its GetType() check altogether.
Since GetHashCode() is not cached and is no more efficient than step 5,
the GetHashCode() comparison is commented out. Similarly, base.Equals()

is not used since the base class is not overriding Equals(). (The assertion
checks that base is not of type object, however it does not check that the
base class overrides Equals(), which is required to appropriately call
base.Equals().) Regardless, since GetHashCode() does not necessarily
return a unique value (it only identifies when operands are different), on its
own it does not conclusively identify equal objects.
From the Library of Wow! eBook
ptg
Operator Overloading 369
Like GetHashCode(), Equals() should also never throw any exceptions.
It is valid to compare any object with any other object, and doing so should
never result in an exception.
Guidelines for Implementing Equality
While learning the details for overriding an object’s virtual members, sev-
eral guidelines emerge.
• Equals(), the == operator, and the != operator should be imple-
mented together.
• A type should use the same algorithm within Equals(), ==, and !=
implementations.
• When implementing Equals(), ==, and !=, a type’s GetHashCode()
method should also be implemented.
• GetHashCode(), Equals(), ==, and != should never throw exceptions.
• When implementing IComparable, equality-related methods should
also be implemented.
Operator Overloading
The preceding section looked at overriding Equals() and provided the
guideline that the class should also implement == and !=. The term for
implementing any operator is operator overloading, and this section
describes how to do this, not only for == and !=, but also for other sup-
ported operators.

For example, string provides a + operator that concatenates two
strings. This is perhaps not surprising, because string is a predefined type,
so it could possibly have special compiler support. However, C# provides
for adding + operator support to a class or struct. In fact, all operators are
supported except x.y, f(x), new, typeof, default, checked, unchecked,
delegate, is, as, =, and =>. One particular noteworthy operator that can-
not be implemented is the assignment operator; there is no way to change
the behavior of the = operator.
From the Library of Wow! eBook

×