Tải bản đầy đủ - 0 (trang)
Chapter 5. C# in the .NET Framework

Chapter 5. C# in the .NET Framework

Tải bản đầy đủ - 0trang

System.Object

As we have already seen, every type in C#, whether it is a value type or a reference type,

ultimately inherits from the root class System.Object. C# provides object as a keyword

alias for this root class. The class ValueType inherits directly from object. ValueType is

the root for all value types, such as structures and simple types like int and decimal.



Public Instance Methods of object

There are four public instance methods of object, three of which are virtual and

frequently overridden by classes.

Equals



public virtual bool Equals(object obj);

This method compares an object with the object passed as a parameter and returns true if

they are equal. object implements this method to test for reference equality. ValueType

overrides the method to test for content equality. Many classes override the method to

make equality behave appropriately for the particular class.

ToString



public virtual string ToString();

This method returns a human-readable string representation of the object. The default

implementation returns the type name. Derived classes frequently override this method to

return a meaningful string representation of the particular object.

GetHashCode



public virtual int GetHashCode();

This method returns a hash value for an object, suitable for use in hashing algorithms and

hash tables. You should normally override this method if you override ToString. (The

C# compiler will give you a warning message if you override one and not the other.)

GetType



public Type GetType();

This method returns type information for the object. This type information can be used to

get the associated metadata through reflection, a topic we discuss in Chapter 8.



Protected Instance Methods

There are two protected instance methods, which can be used only within derived classes.

MemberWiseClone



protected object MemberwiseClone();

This method creates a shallow copy of the object. To perform a deep copy, you should

implement the ICloneable interface We will discuss shallow and deep copy later in this

chapter.

Finalize



~Object();

This method allows an object to free resources and perform other cleanup operations

before it is reclaimed by garbage collection. In C# the Finalize method is represented by

"destructor" notation like that used in C++. But note that the semantics are totally

different. In C++, destructors are invoked in a deterministic manner, which the

programmer can depend upon. In C#, finalization is nondeterministic, dependent upon the

garbage collector. We discuss finalization in Chapter 8.



Generic Interfaces and Standard Behavior

If you are used to a language like Smalltalk, the set of behaviors specified in object may

seem quite limited. Smalltalk, which introduced the concept of a class hierarchy rooted in

a common base class, has a very rich set of methods defined in its Object class. I counted

38 methods! [1] These additional methods support features such as comparing objects and

copying objects. The .NET Framework class library has similar methods, and many more.

But rather than putting them all in a common root class, .NET defines a number of

standard interfaces, which classes can optionally support. This kind of organization,

which is also present in Microsoft's Component Object Model (COM) and in Java, is very

flexible. We will study interfaces later in this chapter, and we will discuss some of the

generic interfaces of the .NET Framework.

[1]



The methods of Smalltalk's Object class are described in Chapters 6 and 14

of Smalltalk-80: The Language and its Implementation, by Adele Goldberg and

David Robson.



Using object Methods in the Customer Class

As a simple illustration of object methods, let's look at our Customer class before and

after overriding the Equals, ToString, and GetHashCode methods.



Default Methods of Object

If our class does not provide any overrides of the virtual instance methods of object, our

class will inherit the standard behavior. This behavior is demonstrated in

CustomerObject\Step1.



// Customer.cs

public class Customer

{

public int CustomerId;

public string FirstName;

public string LastName;

public string EmailAddress;

public Customer(int id, string first, string last,

string email)

{

CustomerId = id;

FirstName = first;

LastName = last;

EmailAddress = email;

}

}

Here is the test program:



// TestCustomer.cs

using System;

public class TestCustomer

{

public static void Main()

{

Customer cust1, cust2;

cust1 = new Customer(99, "John", "Doe",

"john@rocky.com");

cust2 = new Customer(99, "John", "Doe",

"john@rocky.com");

ShowCustomerObject("cust1", cust1);

ShowCustomerObject("cust2", cust2);

CompareCustomerObjects(cust1, cust2);

}

private static void ShowCustomerObject(string label,



Customer cust)

{

Console.WriteLine("---- {0} ----", label);

Console.WriteLine("ToString() = {0}",

cust.ToString());

Console.WriteLine("GetHashCode() = {0}",

cust.GetHashCode());

Console.WriteLine("GetType() = {0}",

cust.GetType());

}

private static void CompareCustomerObjects(

Customer cust1, Customer cust2)

{

Console.WriteLine("Equals() = {0}",

cust1.Equals(cust2));

}

}

Run the test program and you will see this output:



---- cust1 ---ToString() = Customer

GetHashCode() = 4

GetType() = Customer

---- cust2 ---ToString() = Customer

GetHashCode() = 6

GetType() = Customer

Equals() = False

The default implementation is not at all what we want for our Customer object.

ToString returns the name of the class, not information about a particular customer.

Equals checks for reference equality. In our example, we have two different references to

Customer objects with the same content, and Equals return false.

Overriding Methods of Object

The version of the project in CustomerObject\Step2 demonstrates overriding these

virtual methods. Our override of Equals tests for content equality.



// Customer.cs

public class Customer

{



public

public

public

public

public



int CustomerId;

string FirstName;

string LastName;

string EmailAddress;

Customer(int id, string first, string last,

string email)



{

CustomerId = id;

FirstName = first;

LastName = last;

EmailAddress = email;

}

public override bool Equals(object obj)

{

Customer cust = (Customer) obj;

return (cust.CustomerId == CustomerId);

}

public override int GetHashCode()

{

return CustomerId;

}

public override string ToString()

{

return FirstName + " " + LastName ;

}

}

The test program is identical. Here is the new output:



---- cust1 ---ToString() = John Doe

GetHashCode() = 99

GetType() = Customer

---- cust2 ---ToString() = John Doe

GetHashCode() = 99

GetType() = Customer

Equals() = True



Collections

The .NET Framework class library provides an extensive set of classes for working with

collections of objects. These classes are all in the System.Collections namespace and

implement a number of different kinds of collections, including lists, queues, stacks, arrays,

and hashtables. The collections contain object instances. Since all types derive ultimately

from object, any built-in or user-defined type may be stored in a collection.

In this section we will look at a representative class in this namespace, ArrayList, and see

how to use array lists in our programs.



ArrayList Example

To get our bearings, let's begin with a simple example of using the ArrayList class. An

array list, as the name suggests, is a list of items stored like an array. An array list can be

dynamically sized and will grow as necessary to accommodate new elements being added.

Collection classes are made up of instances of type object. We will create and manipulate a

collection of Customer objects. We could just as easily create a collection of any other builtin or user-defined type. If our type were a value type, such as int, the instance would be

boxed before being stored in the collection. When the object is extracted from the collection,

it will be unboxed back to int.

Our example program is CustomerCollection. It initializes a list of customers and then lets

the user show the customers, register a new customer, unregister a customer, and change an

email address. A simple "help" method displays the commands that are available:



Enter command, quit to exit

H> help

The following commands are available:

register

register a customer

unregister unregister a customer

email

change email address

show

show customers

quit

exit the program

Before examining the code it would be a good idea to run the program to register a new

customer, show the customers, change an email address, unregister a customer, and show the

customers again. Here is a sample run of the program:



H> show

id (-1 for all): -1

1

Rocket

Squirrel

2

Bullwinkle

Moose

H> register

first name: Bob



rocky@frosbitefalls.com

moose@wossamotta.edu



last name: Oberg

email address: oberg@objectinnovations.com

id = 3

H> email

customer id: 1

email address: rocky@objectinnovations.com

H> unregister

id: 2

H> show

id (-1 for all): -1

1

Rocket

Squirrel

rocky@objectinnovations.com

3

Bob

Oberg

oberg@objectinnovations.com

Customers Class

All the code for this project is in the folder CustomerCollection. The file customer.cs has

code for the Customer and Customers classes. The code for Customer is almost identical

to what we looked at previously. The only addition is a special constructor that instantiates a

Customer object with a specified id. We use this constructor in the Customers class when

we remove an element and when we check if an element is present in the collection.



public class Customer

{

...

public Customer(int id)

{

CustomerId = id;

FirstName = "";

LastName = "";

EmailAddress = "";

}

...

}

The Customers class contains a list of customers, represented by an ArrayList.



public class Customers

{

private ArrayList customers;

public Customers()

{

customers = new ArrayList();

RegisterCustomer("Rocket", "Squirrel",



"rocky@frosbitefalls.com");

RegisterCustomer("Bullwinkle", "Moose",

"moose@wossamotta.edu");

}

public int RegisterCustomer(string firstName,

string lastName, string emailAddress)

{

Customer cust = new Customer(firstName, lastName,

emailAddress);

customers.Add(cust);

return cust.CustomerId;

}

public void UnregisterCustomer(int id)

{

Customer cust = new Customer(id);

customers.Remove(cust);

}

public void ChangeEmailAddress(int id,

string emailAddress)

{

foreach (Customer cust in customers)

{

if (cust.CustomerId == id)

{

cust.EmailAddress = emailAddress;

return;

}

}

throw new Exception("id " + id + " not found");

}

public void ShowCustomers(int id)

{

if (!CheckId(id) && id != -1)

return;

foreach (Customer cust in customers)

{

if (id == -1 || id == cust.CustomerId)

{

string sid =

cust.CustomerId.ToString().PadLeft(4);

string first = cust.FirstName.PadRight(12);

string last = cust.LastName.PadRight(12);

string email = cust.EmailAddress.PadRight(20);

string str = sid + "

" + first + "

" +

last + "

" + email;



Console.WriteLine(str);

}

}

}

private bool CheckId(int id)

{

Customer cust = new Customer(id);

return customers.Contains(cust);

}

}

The lines in the listing in bold show the places where we are using collection class features.

In Chapter 3 we have already used foreach with arrays. The reason foreach can be used

with arrays is that the Array class, like ArrayList, implements the IEnumerable interface

that supports foreach syntax. We will discuss IEnumerable and the other collection

interfaces later in this chapter.

The Add and Remove methods, as their names suggest, are used for adding and removing

elements from a collection. The Remove method searches for an object in the collection that

Equals the object passed as a parameter. Our special constructor creates an object having the

id of the element we want to remove. Since we provided an override of the Equals method

that bases equality on CustomerId, the proper element will be removed.

Similarly, the Contains method used in our CheckId helper method also relies on the

override of the Equals method.

Compare the code in this program with the use of arrays in the code in the previous chapter's

case study. The collection code is much simpler. Using collections makes it easy to remove

elements as well as add them. Using arrays, you would have to write special code to move

array elements to fill in the space where an element was deleted. Also, collections are not

declared to have a specific size, but can grow as required.



Interfaces

Interface is a very fundamental concept in computer programming. A large system is inevitably

decomposed into parts, and it is critical to precisely specify the interfaces between these parts.

Interfaces should be quite stable, as changing an interface affects multiple parts of the system.

In C# interface is a keyword and has a very precise meaning. An interface is a reference type,

similar to an abstract class, that specifies behavior as a set of methods, properties, indexers, and

events. [2] An interface is a contract. When a class or struct implements an interface, it must

adhere to the contract.

[2]



We discuss events later in this chapter.



Interfaces are a useful way to partition functionality. You should first specify interfaces and

then design appropriate classes to implement the interfaces. While a class in C# can inherit from

only one other class, it can implement multiple interfaces.

Interfaces facilitate dynamic programs—you can query a class at runtime to see whether it

supports a particular interface, and take action accordingly. Interfaces in C# and .NET are

conceptually very similar to interfaces in Microsoft's Component Object Model, but as we will

see, they are much easier to work with.

In this section we will study the fundamentals of interfaces and provide illustrations using some

small sample programs. Then we will restructure our Acme case study to take advantage of

interfaces and explore their use in detail. After that we will examine several important generic

interfaces in the .NET library, which will help us gain an understanding of how C# and the

.NET library support each other to help us develop powerful and useful programs.



Interface Fundamentals

Object-oriented programming is a useful paradigm for helping to design and implement large

systems. Using classes helps us to achieve abstraction and encapsulation. Classes are a natural

decomposition of a large system into manageable parts. Inheritance adds another tool for

structuring our system, enabling us to factor out common parts into base classes, helping us to

accomplish greater code reuse.

The main purpose of an interface is to specify a contract independently of implementation. It is

important to understand that conceptually the interfaces come first.

Interfaces in C#

In C# interface is a keyword, and you define an interface in a manner similar to defining a

class. Like classes, interfaces are reference types. The big difference is that there is no

implementation code in an interface; it is pure specification. Also note that an interface can

have properties as well as methods (it could also have other members, such as indexers). As a

naming convention, interface names usually begin with a capital I.

The IAccount interface specifies operations to be performed on a bank account.



interface IAccount

{

void Deposit(decimal amount);

void Withdraw(decimal amount);

decimal Balance {get;}

void Show();

}

This interface illustrates the syntax for declaring the read-only Balance property—you specify

the data type, the property name, and in curly brackets which of set and get apply (only get in

this case, because the property is read-only).

Implementing an Interface

In C# you specify that a class or struct implements an interface by using the colon notation that

is employed for class inheritance. A class can also inherit both from a class and from an

interface. In this case the base class should appear first in the derivation list after the colon.



public class AccountC : Account, IAccount

{

public void Show()[3]

{

Console.WriteLine("balance = {0}", Balance);

}

}

[3]



Note that we do not need the override keyword when our class implements the

Show method of the IAccount interface. Unlike overriding a virtual method in a class,

we are implementing a method which was only specified but not implemented in the

interface definition.



In our example the class AccountC inherits from the class Account, and it implements the

interface IAccount. The methods of the interface must all be implemented by Account, either

directly or in one of the base classes in its inheritance hierarchy.

We will examine a full-blown example of interfaces with the reservation-broker inheritance

hierarchy later in the chapter, when we implement Step 2 of the case study.

As a small example, consider the program InterfaceDemo. The interface IAccount is defined,

and two different classes, AccountC and AccountW, implement the interface. These

implementations differ only in the Show method. The AccountC implementation performs

console output to display the account balance, and AccountW uses a Windows message box. [4]

The Deposit and Withdraw methods and the Balance property are all implemented in the

Account base class.

[4]



We will discuss Windows programming in Chapter 6. The example program has all

needed references to libraries, and all you need to do to display a message box is to

call the Show method of the MessageBox class.



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Chapter 5. C# in the .NET Framework

Tải bản đầy đủ ngay(0 tr)

×