Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp And The .NET 2.0 Platform (2005) [eng]

.pdf
Скачиваний:
92
Добавлен:
16.08.2013
Размер:
10.35 Mб
Скачать

114 C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

Overriding System.Object.ToString()

Overriding the ToString() method provides a way to quickly gain a snapshot of an object’s current state. As you might guess, this can be helpful during the debugging process. To illustrate, let’s override System.Object.ToString() to return a textual representation of a person’s state (note we are using a new namespace named System.Text):

// Need to reference System.Text to access StringBuilder type. using System;

using System.Text;

class Person

{

// Overriding System.Object.ToString(). public override string ToString()

{

StringBuilder sb = new StringBuilder(); sb.AppendFormat("[FirstName={0};", this.firstName); sb.AppendFormat(" LastName={0};", this.lastName); sb.AppendFormat(" SSN={0};", this.SSN); sb.AppendFormat(" Age={0}]", this.age);

return sb.ToString();

}

...

}

How you format the string returned from System.Object.ToString() is largely a matter of personal choice. In this example, the name/value pairs have been contained within square brackets, with each pair separated by a semicolon (a common technique within the .NET base class libraries).

Also notice that this example makes use of a new type, System.Text.StringBuilder (which is also a matter of personal choice). This type is described in greater detail later in the chapter. The short answer, however, is that StringBuilder is a more efficient alternative to C# string concatenation.

Overriding System.Object.Equals()

Let’s also override the behavior of System.Object.Equals() to work with value-based semantics. Recall that by default, Equals() returns true only if the two references being compared are pointing to the same object on the heap. In many cases, however, you don’t necessary care if two references are pointing to the same object in memory, but you are more interested if the two objects have the same state data (name, SSN, and age in the case of a Person):

public override bool Equals(object o)

{

//Make sure the caller sent a valid

//Person object before proceeding. if (o != null && o is Person)

{

//Now see if the incoming Person

//has the exact same information as

//the current object (this).

Person temp = (Person)o;

if (temp.firstName == this.firstName && temp.lastName == this.lastName && temp.SSN == this.SSN &&

temp.age == this.age) return true;

}

C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

115

return false;

// Not the same!

}

Here you are first verifying the caller did indeed pass in a Person object to the Equals() method using the C# is keyword. After this point, you go about examining the values of the incoming parameter against the values of the current object’s field data (note the use of the this keyword, which refers to the current object).

The prototype of System.Object.Equals() takes a single argument of type object. Thus, you are required to perform an explicit cast within the Equals() method to access the members of the Person type. If the name, SSN, and age of each are identical, you have two objects with the same state data and therefore return true. If any point of data is not identical, you return false.

If you override System.Object.ToString() for a given class, you can take a very simple shortcut when overriding System.Object.Equals(). Given that the value returned from ToString() should take into account all of the member variables of the current class (and possible data declared in base classes), Equals() can simply compare the values of the string types:

public override bool Equals(object o)

{

if (o != null && o is Person)

{

Person temp = (Person)o;

if (this.ToString() == o.ToString()) return true;

else

return false;

}

return false;

}

Now, for the sake of argument, assume you have a type named Car, and attempt to pass in a Car instance to the Person.Equals() method as so:

// Cars are not people!

Car c = new Car(); Person p = new Person(); p.Equals(c);

Given your runtime check for a true-blue Person object (via the is operator) the Equals() method returns false. Now consider the following invocation:

// Oops!

Person p = new Person(); p.Equals(null);

This would also be safe, given your check for an incoming null reference.

Overriding System.Object.GetHashCode()

When a class overrides the Equals() method, best practices dictate that you should also override System.Object.GetHashCode(). If you fail to do so, you are issued a compiler warning. The role of GetHashCode() is to return a numerical value that identifies an object based on its internal state data. Thus, if you have two Person objects that have an identical first name, last name, SSN, and age, you should obtain the same hash code.

By and large, overriding this method is only useful if you intend to store a custom type within a hash-based collection such as System.Collections.Hashtable. Under the hood, the Hashtable type calls the Equals() and GetHashCode() members of the contained types to determine the correct object to return to the caller. Due to the fact that System.Object has no clue about the state data of derived types, you should override this member for any type you wish to store in a Hashtable.

116 C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

There are many algorithms that can be used to create a hash code—some fancy, others not so fancy. As mentioned, an object’s hash value will be based on its state data. As luck would have it, the System.String class has a very solid implementation of GetHashCode() that is based on the string’s character data. Therefore, if you can identify a string field that should be unique among objects (such as the Person’s SSN field), you can simply call GetHashCode() on the field’s string representation:

// Return a hash code based on the person's SSN. public override int GetHashCode()

{

return SSN.GetHashCode();

}

If you cannot identify a single point of data in your class, but have overridden ToString(), you can simply return the hash code of the string returned from your custom ToString() implementation:

// Return a hash code based our custom ToString(). public override int GetHashCode()

{

return ToString().GetHashCode();

}

Testing the Overridden Members

You can now test your updated Person class. Add the following code to your Main() method and check out Figure 3-18 for output:

static void Main(string[] args)

{

//NOTE: We want these to be identical for testing purposes.

Person p3 = new Person("Fred", "Jones", "222-22-2222", 98); Person p4 = new Person("Fred", "Jones", "222-22-2222", 98);

//Should have same hash code and string at this point.

Console.WriteLine("-> Hash code of p3 = {0}", p3.GetHashCode()); Console.WriteLine("-> Hash code of p4 = {0}", p4.GetHashCode()); Console.WriteLine("-> String of p3 = {0}", p3.ToString()); Console.WriteLine("-> String of p4 = {0}", p4.ToString());

//Should be equal at this point.

if (p3.Equals(p4))

Console.WriteLine("-> P3 and p4 have same state!");

else

Console.WriteLine("-> P3 and p4 have different state!");

// Change age of p4.

Console.WriteLine("\n-> Changing the age of p4\n"); p4.age = 2;

// No longer equal, different hash values and string data.

Console.WriteLine("-> String of p3 = {0}", p3.ToString()); Console.WriteLine("-> String of p4 = {0}", p4.ToString()); Console.WriteLine("-> Hash code of p3 = {0}", p3.GetHashCode()); Console.WriteLine("-> Hash code of p4 = {0}", p4.GetHashCode()); if (p3.Equals(p4))

Console.WriteLine("-> P3 and p4 have same state!");

else

Console.WriteLine("-> P3 and p4 have different state!");

}

C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

117

Figure 3-18. Overridden System.Object members in action

Static Members of System.Object

To wrap up our examination of this supreme base class of .NET, it is worth pointing out that System. Object does define two static members (Object.Equals() and Object.ReferenceEquals()) that test for value-based or reference-based equality. Consider the following code:

static void Main(string[] args)

{

// Assume two identically configured objects.

Person p3 = new Person("Fred", "Jones", "222-22-2222", 98); Person p4 = new Person("Fred", "Jones", "222-22-2222", 98);

// Do p3 and p4 have the same state? TRUE!

Console.WriteLine("Do P3 and p4 have same state: {0} ", object.Equals(p3, p4));

// Are they the same object in memory? FALSE!

Console.WriteLine("Are P3 and p4 are pointing to same object: {0} ", object.ReferenceEquals(p3, p4));

}

Source Code The ObjectMethods project is located under the Chapter 3 subdirectory.

The System Data Types (and C# Shorthand

Notation)

As you may have begun to notice, every intrinsic C# data type is actually a shorthand notation for defining an existing type defined in the System namespace. Table 3-11 lists each system data type, its range, the corresponding C# alias, and the type’s compliance with the Common Language Specification (CLS).

118 C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

Table 3-11. System Types and C# Shorthand

C#

CLS

 

 

 

Shorthand

Compliant?

System Type

Range

Meaning in Life

sbyte

No

System.SByte

–128 to 127

Signed 8-bit number

byte

Yes

System.Byte

0 to 255

Unsigned 8-bit

 

 

 

 

number

short

Yes

System.Int16

–32,768 to 32,767

Signed 16-bit number

ushort

No

System.UInt16

0 to 65,535

Unsigned 16-bit

 

 

 

 

number

int

Yes

System.Int32

–2,147,483,648 to

Signed 32-bit number

 

 

 

2,147,483,647

 

uint

No

System.UInt32

0 to 4,294,967,295

Unsigned 32-bit

 

 

 

 

number

long

Yes

System.Int64

–9,223,372,036,854,775,808

Signed 64-bit number

 

 

 

to 9,223,372,036,854,775,807

 

ulong

No

System.UInt64

0 to

Unsigned 64-bit

 

 

 

18,446,744,073,709,551,615

number

char

Yes

System.Char

U0000 to Uffff

A single 16-bit

 

 

 

 

Unicode character

float

Yes

System.Single

1.5×10-45 to 3.4×1038

32-bit floating point

 

 

 

 

number

double

Yes

System.Double

5.0×10-324 to 1.7×10308

64-bit floating point

 

 

 

 

number

bool

Yes

System.Boolean

true or false

Represents truth or

 

 

 

 

falsity

decimal

Yes

System.Decimal

100 to 1028

A 96-bit signed

 

 

 

 

number

string

Yes

System.String

Limited by system memory

Represents a set of

 

 

 

 

Unicode characters

object

Yes

System.Object

Any type can be stored

 

 

 

in an object variable

The base class of all types in the .NET universe

Note By default, a real numeric literal on the right-hand side of the assignment operator is treated as double. Therefore, to initialize a float variable, use the suffix f or F (5.3F).

It is very interesting to note that even the primitive .NET data types are arranged in a class hierarchy. The relationship between these core system types (as well as some other soon-to-be-discovered types) can be represented as shown in Figure 3-19.

 

 

 

 

 

 

 

 

 

 

 

 

C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

119

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 3-19. The hierarchy of System types

As you can see, each of these types ultimately derives from System.Object. Because data types such as int are simply shorthand notations for the corresponding system type (in this case, System.Int32), the following is perfectly legal syntax:

// Remember! A C# int is really a shorthand for System.Int32.

Console.WriteLine(12.GetHashCode());

Console.WriteLine(12.Equals(23));

Console.WriteLine(12.ToString());

Console.WriteLine(12); // ToString() called automatically.

Console.WriteLine(12.GetType().BaseType);

Furthermore, given that all value types are provided with a default constructor, it is permissible to create a system type using the new keyword, which sets the variable to its default value. Although it is more cumbersome to use the new keyword when creating a System data type, the following is syntactically well-formed C#:

120 C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

// These statements are identical. bool b1 = new bool(); // b1 = false. bool b2 = false;

On a related note, you could also create a system data type using the fully qualified name:

// These statements are also semantically identical.

System.Bool b1 = new System.Bool(); // b1 = false. System.Bool sb2 = false;

Experimenting with Numerical Data Types

The numerical types of .NET support MaxValue and MinValue properties that provide information regarding the range a given type can store. Assume you have created some variables of type System.UInt16 (an unsigned short) and exercised it as follows:

static void Main(string[] args)

{

System.UInt16 myUInt16 = 30000;

Console.WriteLine("Max for an UInt16 is: {0} ", UInt16.MaxValue); Console.WriteLine("Min for an UInt16 is: {0} ", UInt16.MinValue); Console.WriteLine("Value is: {0} ", myUInt16); Console.WriteLine("I am a: {0} ", myUInt16.GetType());

// Now in System.UInt16 shorthand (e.g., a ushort). ushort myOtherUInt16 = 12000;

Console.WriteLine("Max for an UInt16 is: {0} ", ushort.MaxValue); Console.WriteLine("Min for an UInt16 is: {0} ", ushort.MinValue); Console.WriteLine("Value is: {0} ", myOtherUInt16); Console.WriteLine("I am a: {0} ", myOtherUInt16.GetType()); Console.ReadLine();

}

In addition to the MinValue/MaxValue properties, a given system type may define further useful members. For example, the System.Double type allows you to obtain the values for Epsilon and infinity values:

Console.WriteLine("-> double.Epsilon: {0}", double.Epsilon); Console.WriteLine("-> double.PositiveInfinity: {0}", double.PositiveInfinity); Console.WriteLine("-> double.NegativeInfinity: {0}", double.NegativeInfinity); Console.WriteLine("-> double.MaxValue: {0}", double.MaxValue); Console.WriteLine("-> double.MinValue: {0}",double.MinValue);

Members of System.Boolean

Next, consider the System.Boolean data type. Unlike C(++), the only valid assignment a C# bool can take is from the set {true | false}. You cannot assign makeshift values (e.g., –1, 0, 1) to a C# bool, which (to most programmers) is a welcome change. Given this point, it should be clear that

System.Boolean does not support a MinValue/MaxValue property set, but rather TrueString/FalseString:

// No more ad hoc Boolean types in C#!

bool b = 0;

//

Illegal!

bool b2

= -1;

//

Also illegal!

bool b3

= true;

//

No problem.

bool b4

= false;

//

No problem.

Console.WriteLine("->

bool.FalseString: {0}", bool.FalseString);

Console.WriteLine("->

bool.TrueString: {0}", bool.TrueString);

C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

121

Members of System.Char

C# textual data is represented by the intrinsic C# string and char data types. All .NET-aware languages map textual data to the same underlying types (System.String and System.Char), both of which are Unicode under the hood.

The System.Char type provides you with a great deal of functionality beyond the ability to hold a single point of character data (which must be placed between single quotes). Using the static methods of System.Char, you are able to determine if a given character is numerical, alphabetical, a point of punctuation, or whatnot. To illustrate, check out the following:

static void Main(string[] args)

{

...

// Test the truth of the following statements...

Console.WriteLine("-> char.IsDigit('K'): {0}", char.IsDigit('K')); Console.WriteLine("-> char.IsDigit('9'): {0}", char.IsDigit('9')); Console.WriteLine("-> char.IsLetter('10', 1): {0}", char.IsLetter("10", 1)); Console.WriteLine("-> char.IsLetter('p'): {0}", char.IsLetter('p')); Console.WriteLine("-> char.IsWhiteSpace('Hello There', 5): {0}",

char.IsWhiteSpace("Hello There", 5));

Console.WriteLine("-> char.IsWhiteSpace('Hello There', 6): {0}", char.IsWhiteSpace("Hello There", 6));

Console.WriteLine("-> char.IsLetterOrDigit('?'): {0}", char.IsLetterOrDigit('?'));

Console.WriteLine("-> char.IsPunctuation('!'): {0}", char.IsPunctuation('!'));

Console.WriteLine("-> char.IsPunctuation('>'): {0}", char.IsPunctuation('>'));

Console.WriteLine("-> char.IsPunctuation(','): {0}", char.IsPunctuation(','));

...

}

As you can see, each of these static members of System.Char has two calling conventions: a single character or a string with a numerical index that specified the position of the character to test.

Parsing Values from String Data

Also understand that the .NET data types provide the ability to generate a variable of their underlying type given a textual equivalent (e.g., parsing). This technique can be extremely helpful when you wish to convert a bit of user input data (such as a selection from a drop-down list) into a numerical value. Ponder the following parsing logic:

static void Main(string[] args)

{

...

bool myBool = bool.Parse("True"); Console.WriteLine("-> Value of myBool: {0}", myBool); double myDbl = double.Parse("99.884"); Console.WriteLine("-> Value of myDbl: {0}", myDbl); int myInt = int.Parse("8");

Console.WriteLine("-> Value of myInt: {0}", myInt); char myChar = char.Parse("w");

Console.WriteLine("-> Value of myChar: {0}\n", myChar);

...

}

122 C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

System.DateTime and System.TimeSpan

To wrap up our examination of core data types, allow me to point out the fact that the System namespace defines a few useful data types for which there is no C# keyword—specifically, the DateTime and TimeSpan structures (I’ll leave the investigation of System.Guid and System.Void, as shown in Figure 3-19, to interested readers).

The DateTime type contains data that represents a specific date (month, day, year) and time value, both of which may be formatted in a variety of ways using the supplied members. By way of a simple example, ponder the following statements:

static void Main(string[] args)

{

...

//This constructor takes (year, month, day)

DateTime dt = new DateTime(2004, 10, 17);

//What day of the month is this?

Console.WriteLine("The day of {0} is {1}", dt.Date, dt.DayOfWeek); dt.AddMonths(2); // Month is now December.

Console.WriteLine("Daylight savings: {0}", dt.IsDaylightSavingTime());

...

}

The TimeSpan structure allows you to easily define and transform units of time using various members, for example:

static void Main(string[] args)

{

...

//This constructor takes (hours, minutes, seconds)

TimeSpan ts = new TimeSpan(4, 30, 0); Console.WriteLine(ts);

//Subtract 15 minutes from the current TimeSpan and

//print the result.

Console.WriteLine(ts.Subtract(new TimeSpan(0, 15, 0)));

...

}

Figure 3-20 shows the output of the DateTime and TimeSpan statements.

Figure 3-20. Working with DateTime and TimeSpan

C H A P T E R 3 C # L A N G U A G E F U N D A M E N TA L S

123

Source Code The DataTypes project is located under the Chapter 3 subdirectory.

The System.String Data Type

The C# string keyword is a shorthand notation of the System.String type, which provides a number of members you would expect from such a utility class. Table 3-12 lists some (but not all) of the interesting members.

Table 3-12. Select Members of System.String

Member

Meaning in Life

Length

This property returns the length of the current string.

Contains()

This method is used to determine if the current string object contains

 

a specified string.

Format()

This static method is used to format a string literal using other primitives

 

(i.e., numerical data and other strings) and the {0} notation examined earlier

 

in this chapter.

Insert()

This method is used to receive a copy of the current string that contains

 

newly inserted string data.

PadLeft()

These methods return copies of the current string that has been padded

PadRight()

with specific data.

Remove()

Use these methods to receive a copy of a string, with modifications

Replace()

(characters removed or replaced).

Substring()

This method returns a string that represents a substring of the current string.

ToCharArray()

This method returns a character array representing the current string.

ToUpper()

These methods create a copy of a given string in uppercase or lowercase.

ToLower()

 

 

 

Basic String Operations

To illustrate some basic string operations, consider the following Main() method:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Strings *****");

string s = "Boy, this is taking a long time."; Console.WriteLine("--> s contains 'oy'?: {0}", s.Contains("oy")); Console.WriteLine("--> s contains 'Boy'?: {0}", s.Contains("Boy")); Console.WriteLine(s.Replace('.', '!')); Console.WriteLine(s.Insert(0, "Boy O' "));

Console.ReadLine();

}

Here, we are creating a string type invoking the Contains(), Replace(), and Insert() methods. Figure 3-21 shows the output.