Siêu thị PDFTải ngay đi em, trời tối mất

Thư viện tri thức trực tuyến

Kho tài liệu với 50,000+ tài liệu học thuật

© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

accelerated c# 2010 trey nash phần 9 ppt
PREMIUM
Số trang
73
Kích thước
7.3 MB
Định dạng
PDF
Lượt xem
879

accelerated c# 2010 trey nash phần 9 ppt

Nội dung xem thử

Mô tả chi tiết

CHAPTER 13 ■ IN SEARCH OF C# CANONICAL FORMS

453

You’ve seen how equality tests on references to objects test identity by default. However, there

might be times when an identity equivalence test makes no sense. Consider an immutable object that

represents a complex number:

public class ComplexNumber

{

public ComplexNumber( int real, int imaginary )

{

this.real = real;

this.imaginary = imaginary;

}

private int real;

private int imaginary;

}

public class EntryPoint

{

static void Main()

{

ComplexNumber referenceA = new ComplexNumber( 1, 2 );

ComplexNumber referenceB = new ComplexNumber( 1, 2 );

System.Console.WriteLine( "Result of Equality is {0}",

referenceA == referenceB );

}

}

The output from that code looks like this:

Result of Equality is False

Figure 13-2 shows the diagram representing the in-memory layout of the references.

Figure 13-2. References to ComplexNumber

This is the expected result based upon the default meaning of equality between references.

However, this is hardly intuitive to the user of these ComplexNumber objects. It would make better sense

for the comparison of the two references in the diagram to return true because the values of the two

objects are the same. To achieve such a result, you need to provide a custom implementation of equality

for these objects. I’ll show how to do that shortly, but first, let’s quickly discuss what value equality

means.

CHAPTER 13 ■ IN SEARCH OF C# CANONICAL FORMS

454

Value Equality

From the preceding section, it should be obvious what value equality means. Equality of two values is

true when the actual values of the fields representing the state of the object or value are equivalent. In

the ComplexNumber example from the previous section, value equality is true when the values for the real

and imaginary fields are equivalent between two instances of the class.

In the CLR, and thus in C#, this is exactly what equality means for value types defined as structs.

Value types derive from System.ValueType, and System.ValueType overrides the Object.Equals method.

ValueType.Equals sometimes uses reflection to iterate through the fields of the value type while

comparing the fields. This generic implementation will work for all value types. However, it is much

more efficient if you override the Equals method in your struct types and compare the fields directly.

Although using reflection to accomplish this task is a generally applicable approach, it’s very inefficient.

■ Note Before the implementation of ValueType.Equals resorts to using reflection, it makes a couple of quick

checks. If the two types being compared are different, it fails the equality. If they are the same type, it first checks

to see if the types in the contained fields are simple data types that can be bitwise-compared. If so, the entire type

can be bitwise-compared. Failing both of these conditions, the implementation then resorts to using reflection.

Because the default implementation of ValueType.Equals iterates over the value’s contained fields using

reflection, it determines the equality of those individual fields by deferring to the implementation of

Object.Equals on those objects. Therefore, if your value type contains a reference type field, you might be in for

a surprise, depending on the semantics of the Equals method implemented on that reference type. Generally,

containing reference types within a value type is not recommended.

Overriding Object.Equals for Reference Types

Many times, you might need to override the meaning of equivalence for an object. You might want

equivalence for your reference type to be value equality as opposed to referential equality, or identity.

Or, as you’ll see in a later section, you might have a custom value type where you want to override the

default Equals method provided by System.ValueType in order to make the operation more efficient. No

matter what your reason for overriding Equals, you must follow several rules:

• x.Equals(x) == true. This is the reflexive property of equality.

• x.Equals(y) == y.Equals(x). This is the symmetric property of equality.

• x.Equals(y) && y.Equals(z) implies x.Equals(z) == true. This is the transitive

property of equality.

• x.Equals(y) must return the same result as long as the internal state of x and y has

not changed.

• x.Equals(null) == false for all x that are not null.

• Equals must not throw exceptions.

CHAPTER 13 ■ IN SEARCH OF C# CANONICAL FORMS

455

An Equals implementation should adhere to these hard-and-fast rules. You should follow other

suggested guidelines in order to make the Equals implementations on your classes more robust.

As already discussed, the default version of Object.Equals inherited by classes tests for referential

equality, otherwise known as identity. However, in cases like the example using ComplexNumber, such a

test is not intuitive. It would be natural and expected that instances of such a type are compared on a

field-by-field basis. It is for this very reason that you should override Object.Equals for these types of

classes that behave with value semantics.

Let’s revisit the ComplexNumber example once again to see how you can do this:

public class ComplexNumber

{

public ComplexNumber( int real, int imaginary )

{

this.real = real;

this.imaginary = imaginary;

}

public override bool Equals( object obj )

{

ComplexNumber other = obj as ComplexNumber;

if( other == null )

{

return false;

}

return (this.real == other.real) &&

(this.imaginary == other.imaginary);

}

public override int GetHashCode()

{

return (int) real ^ (int) imaginary;

}

public static bool operator==( ComplexNumber me, ComplexNumber other )

{

return Equals( me, other );

}

public static bool operator!=( ComplexNumber me, ComplexNumber other )

{

return Equals( me, other );

}

private double real;

private double imaginary;

}

public class EntryPoint

{

static void Main()

{

CHAPTER 13 ■ IN SEARCH OF C# CANONICAL FORMS

456

ComplexNumber referenceA = new ComplexNumber( 1, 2 );

ComplexNumber referenceB = new ComplexNumber( 1, 2 );

System.Console.WriteLine( "Result of Equality is {0}",

referenceA == referenceB );

// If we really want referential equality.

System.Console.WriteLine( "Identity of references is {0}",

(object) referenceA == (object) referenceB );

System.Console.WriteLine( "Identity of references is {0}",

ReferenceEquals(referenceA, referenceB) );

}

}

In this example, you can see that the implementation of Equals is pretty straightforward, except that

I do have to test some conditions. I must make sure that the object reference I’m comparing to is both

not null and does, in fact, reference an instance of ComplexNumber. Once I get that far, I can simply test

the fields of the two references to make sure they are equal. You could introduce an optimization and

compare this with other in Equals. If they’re referencing the same object, you could return true without

comparing the fields. However, comparing the two fields is a trivial amount of work in this case, so I’ll

skip the identity test.

In the majority of cases, you won’t need to override Object.Equals for your reference type objects. It

is recommended that your objects treat equivalence using identity comparisons, which is what you get

for free from Object.Equals. However, there are times when it makes sense to override Equals for an

object. For example, if your object represents something that naturally feels like a value and is

immutable, such as a complex number or the System.String class, then it could very well make sense to

override Equals in order to give that object’s implementation of Equals() value equality semantics.

In many cases, when overriding virtual methods in derived classes, such as Object.Equals, it makes

sense to call the base class implementation at some point. However, if your object derives directly from

System.Object, it makes no sense to do this. This is because Object.Equals likely carries a different

semantic meaning from the semantics of your override. Remember, the only reason to override Equals

for objects is to change the semantic meaning from identity to value equality. Also, you don’t want to

mix the two semantics together. But there’s an ugly twist to this story. You do need to call the base class

version of Equals if your class derives from a class other than System.Object and that other class does

override Equals to provide the same semantic meaning you intend in your derived type. This is because

the most likely reason a base class overrode Object.Equals is to switch to value semantics. This means

that you must have intimate knowledge of your base class if you plan on overriding Object.Equals, so

that you will know whether to call the base version. That’s the ugly truth about overriding Object.Equals

for reference types.

Sometimes, even when you’re dealing with reference types, you really do want to test for referential

equality, no matter what. You cannot always rely on the Equals method for the object to determine the

referential equality, so you must use other means because the method can be overridden as in the

ComplexNumber example.

Thankfully, you have two ways to handle this job, and you can see them both at the end of the Main

method in the previous code sample. The C# compiler guarantees that if you apply the == operator to

two references of type Object, you will always get back referential equality. Also, System.Object supplies

a static method named ReferenceEquals that takes two reference parameters and returns true if the

identity test holds true. Either way you choose to go, the result is the same.

If you do change the semantic meaning of Equals for an object, it is best to document this fact

clearly for the clients of your object. If you override Equals for a class, I would strongly recommend that

you tag its semantic meaning with a custom attribute, similar to the technique introduced for

iCloneable implementations previously. This way, people who derive from your class and want to

change the semantic meaning of Equals can quickly determine if they should call your implementation

CHAPTER 13 ■ IN SEARCH OF C# CANONICAL FORMS

457

in the process. For maximum efficiency, the custom attribute should serve a documentation purpose.

Although it’s possible to look for such an attribute at run time, it would be very inefficient.

■ Note You should never throw exceptions from an implementation of Object.Equals. Instead of throwing an

exception, return false as the result instead.

Throughout this entire discussion, I have purposely avoided talking about the equality operators

because it is beneficial to consider them as an extra layer in addition to Object.Equals. Support of

operator overloading is not a requirement for languages to be CLS-compliant. Therefore, not all

languages that target the CLR support them thoroughly. Visual Basic is one language that has taken a

while to support operator overloading, and it only started supporting it fully in Visual Basic 2005. Visual

Basic .NET 2003 supports calling overloaded operators on objects defined in languages that support

overloaded operators, but they must be called through the special function name generated for the

operator. For example, operator== is implemented with the name op_Equality in the generated IL code.

The best approach is to implement Object.Equals as appropriate and base any operator== or operator!=

implementations on Equals while only providing them as a convenience for languages that support

them.

■ Note Consider implementing IEquatable<T> on your type to get a type-safe version of Equals. This is

especially important for value types, because type-specific versions of methods avoid unnecessary boxing.

If You Override Equals, Override GetHashCode Too

GetHashCode is called when objects are used as keys of a hash table. When a hash table searches for an

entry after given a key to look for, it asks the key for its hash code and then uses that to identify which

hash bucket the key lives in. Once it finds the bucket, it can then see if that key is in the bucket.

Theoretically, the search for the bucket should be quick, and the buckets should have very few keys in

them. This occurs if your GetHashCode method returns a reasonably unique value for instances of your

object that support value equivalence semantics.

Given the previous discussion, you can see that it would be very bad if your hash code algorithm

could return a different value between two instances that contain values that are equivalent. In such a

case, the hash table might fail to find the bucket your key is in. For this reason, it is imperative that you

override GetHashCode if you override Equals for an object. In fact, if you override Equals and not

GetHashCode, the C# compiler will let you know about it with a friendly warning. And because we’re all

diligent with regard to building our release code with zero warnings, we should take the compiler’s word

seriously.

CHAPTER 13 ■ IN SEARCH OF C# CANONICAL FORMS

458

■ Note The previous discussion should be plenty of evidence that any type used as a hash table key should be

immutable. After all, the GetHashCode value is normally computed based upon the state of the object itself. If that

state changes, the GetHashCode result will likely change with it.

GetHashCode implementations should adhere to the following rules:

• If, for two instances, x.Equals(y) is true, then x.GetHashCode() ==

y.GetHashCode().

• Hash codes generated by GetHashCode need not be unique.

• GetHashCode is not permitted to throw exceptions.

If two instances return the same hash code value, they must be further compared with Equals to

determine whether they’re equivalent. Incidentally, if your GetHashCode method is very efficient, you can

base the inequality code path of your operator!= and operator== implementations on it because

different hash codes for objects of the same type imply inequality. Implementing the operators this way

can be more efficient in some cases, but it all depends on the efficiency of your GetHashCode

implementation and the complexity of your Equals method. In some cases, when using this technique,

the calls to the operators could be less efficient than just calling Equals, but in other cases, they can be

remarkably more efficient. For example, consider an object that models a multidimensional point in

space. Suppose that the number of dimensions (rank) of this point could easily approach into the

hundreds. Internally, you could represent the dimensions of the point by using an array of integers. Say

you want to implement the GetHashCode method by computing a CRC32 on the dimension points in the

array. This also implies that this Point type is immutable. This GetHashCode call could potentially be

expensive if you compute the CRC32 each time it is called. Therefore, it might be wise to precompute the

hash and store it in the object. In such a case, you could write the equality operators as shown in the

following code:

sealed public class Point

{

// other methods removed for clarity

public override bool Equals( object other ) {

bool result = false;

Point that = other as Point;

if( that != null ) {

if( this.coordinates.Length !=

that.coordinates.Length ) {

result = false;

} else {

result = true;

for( long i = 0;

i < this.coordinates.Length;

++i ) {

if( this.coordinates[i] !=

that.coordinates[i] ) {

result = false;

break;

CHAPTER 13 ■ IN SEARCH OF C# CANONICAL FORMS

459

}

}

}

}

return result;

}

public override int GetHashCode() {

return precomputedHash;

}

public static bool operator ==( Point pt1, Point pt2 ) {

if( pt1.GetHashCode() != pt2.GetHashCode() ) {

return false;

} else {

return Object.Equals( pt1, pt2 );

}

}

public static bool operator !=( Point pt1, Point pt2 ) {

if( pt1.GetHashCode() != pt2.GetHashCode() ) {

return true;

} else {

return !Object.Equals( pt1, pt2 );

}

}

private float[] coordinates;

private int precomputedHash;

}

In this example, as long as the precomputed hash is sufficiently unique, the overloaded operators

will execute quickly in some cases. In the worst case, one more comparison between two integers—the

hash values—is executed along with the function calls to acquire them. If the call to Equals is expensive,

then this optimization will return some gains on a lot of the comparisons. If the call to Equals is not

expensive, then this technique could add overhead and make the code less efficient. It’s best to apply the

old adage that premature optimization is poor optimization. You should only apply such an

optimization after a profiler has pointed you in this direction and if you’re sure it will help.

Object.GetHashCode exists because the developers of the Standard Library felt it would be

convenient to be able to use any object as a key to a hash table. The fact is, not all objects are good

candidates for hash keys. Usually, it’s best to use immutable types as hash keys. A good example of an

immutable type in the Standard Library is System.String. Once such an object is created, you can never

change it. Therefore, calling GetHashCode on a string instance is guaranteed to always return the same

value for the same string instance. It becomes more difficult to generate hash codes for objects that are

mutable. In those cases, it’s best to base your GetHashCode implementation on calculations performed on

immutable fields inside the mutable object.

Detailing algorithms for generating hash codes is outside the scope of this book. I recommend that

you reference Donald E. Knuth’s The Art of Computer Programming, Volume 3: Sorting and Searching,

Second Edition (Boston: Addison-Wesley Professional, 1998). For the sake of example, suppose that you

want to implement GetHashCode for a ComplexNumber type. One solution is to compute the hash based on

the magnitude of the complex number, as in the following example:

Tải ngay đi em, còn do dự, trời tối mất!