1. Trang chủ
  2. » Công Nghệ Thông Tin

Pro C# 2008 and the .NET 3.5 Platform, Fourth Edition phần 3 ppsx

140 468 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 140
Dung lượng 3,87 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

As an alternative to overriding Finalize, your class could implement the IDisposable interface, which defines a single method named Dispose: public interface IDisposable { void Dispose;

Trang 1

If all generation 0 objects have been evaluated, but additional memory is still required, tion 1 objects are then investigated for their “reachability” and collected accordingly Surviving

genera-generation 1 objects are then promoted to genera-generation 2 If the garbage collector still requires

addi-tional memory, generation 2 objects are then evaluated for their reachability At this point, if ageneration 2 object survives a garbage collection, it remains a generation 2 object given the prede-fined upper limit of object generations

The bottom line is that by assigning a generational value to objects on the heap, newer objects(such as local variables) will be removed quickly, while older objects (such as a program’s applica-tion object) are not “bothered” as often

The System.GC Type

The base class libraries provide a class type named System.GC that allows you to programmaticallyinteract with the garbage collector using a set of static members Now, do be very aware that youwill seldom (if ever) need to make use of this type directly in your code Typically speaking, the onlytime you will make use of the members of System.GC is when you are creating types that make use of

unmanaged resources Table 8-1 provides a rundown of some of the more interesting members

(consult the NET Framework 3.5 SDK documentation for complete details)

Table 8-1.Select Members of the System.GC Type

System.GC Member Meaning in Life

AddMemoryPressure() Allow you to specify a numerical value that represents the RemoveMemoryPressure() calling object’s “urgency level” regarding the garbage collection

process Be aware that these methods should alter pressure in

tandem and thus never remove more pressure than the total

amount you have added

Collect() Forces the GC to perform a garbage collection This method has

been overloaded to specify a generation to collect, as well as themode of collection (via the GCCollectionMode enumeration).CollectionCount() Returns a numerical value representing how many times a given

generation has been swept

GetGeneration() Returns the generation to which an object currently belongs.GetTotalMemory() Returns the estimated amount of memory (in bytes) currently

allocated on the managed heap The Boolean parameter specifieswhether the call should wait for garbage collection to occurbefore returning

MaxGeneration Returns the maximum of generations supported on the target

system Under Microsoft’s NET 3.5, there are three possiblegenerations (0, 1, and 2)

SuppressFinalize() Sets a flag indicating that the specified object should not have its

Finalize() method called

WaitForPendingFinalizers() Suspends the current thread until all finalizable objects have

been finalized This method is typically called directly afterinvoking GC.Collect()

To illustrate how the System.GC type can be used to obtain various garbage collection–centricdetails, consider the following Main() method, which makes use of several members of GC:

www.free-ebooks-download.org

Trang 2

static void Main(string[] args)

{

Console.WriteLine("***** Fun with System.GC *****");

// Print out estimated number of bytes on heap.

Console.WriteLine("Estimated bytes on heap: {0}",

GC.GetTotalMemory(false));

// MaxGeneration is zero based, so add 1 for display purposes.

Console.WriteLine("This OS has {0} object generations.\n",

(GC.MaxGeneration + 1));

Car refToMyCar = new Car("Zippy", 100);

Console.WriteLine(refToMyCar.ToString());

// Print out generation of refToMyCar object.

Console.WriteLine("Generation of refToMyCar is: {0}",

GC.GetGeneration(refToMyCar));

Console.ReadLine();

}

Forcing a Garbage Collection

Again, the whole purpose of the NET garbage collector is to manage memory on our behalf

How-ever, under some very rare circumstances, it may be beneficial to programmatically force a garbage

collection using GC.Collect() Specifically:

• Your application is about to enter into a block of code that you do not wish to be interrupted

by a possible garbage collection

• Your application has just finished allocating an extremely large number of objects and youwish to remove as much of the acquired memory as possible

If you determine it may be beneficial to have the garbage collector check for unreachableobjects, you could explicitly trigger a garbage collection, as follows:

static void Main(string[] args)

{

// Force a garbage collection and wait for

// each object to be finalized.

GC.Collect();

GC.WaitForPendingFinalizers();

}

When you manually force a garbage collection, you should always make a call to GC

WaitForPendingFinalizers() With this approach, you can rest assured that all finalizable objects

have had a chance to perform any necessary cleanup before your program continues forward

Under the hood, GC.WaitForPendingFinalizers() will suspend the calling “thread” during the

col-lection process This is a good thing, as it ensures your code does not invoke methods on an object

currently being destroyed!

The GC.Collect() method can also be supplied a numerical value that identifies the oldestgeneration on which a garbage collection will be performed For example, if you wished to instruct

the CLR to only investigate generation 0 objects, you would write the following:

www.free-ebooks-download.org

Trang 3

static void Main(string[] args)

public enum GCCollectionMode

{

Default, // Forced is the current default

Forced, // Tells the runtime to collect immediately!

Optimized // Allows the runtime to determine

// whether the current time is optimal to reclaim objects

Console.WriteLine("***** Fun with System.GC *****");

// Print out estimated number of bytes on heap.

Console.WriteLine("Estimated bytes on heap: {0}",

GC.GetTotalMemory(false));

// MaxGeneration is zero based.

Console.WriteLine("This OS has {0} object generations.\n",

(GC.MaxGeneration + 1));

Car refToMyCar = new Car("Zippy", 100);

Console.WriteLine(refToMyCar.ToString());

// Print out generation of refToMyCar.

Console.WriteLine("\nGeneration of refToMyCar is: {0}",

GC.GetGeneration(refToMyCar));

// Make a ton of objects for testing purposes.

object[] tonsOfObjects = new object[50000];

for (int i = 0; i < 50000; i++)

tonsOfObjects[i] = new object();

// Collect only gen 0 objects.

GC.Collect(0, GCCollectionMode.Forced);

GC.WaitForPendingFinalizers();

// Print out generation of refToMyCar.

Console.WriteLine("Generation of refToMyCar is: {0}",

GC.GetGeneration(refToMyCar));

www.free-ebooks-download.org

Trang 4

// See if tonsOfObjects[9000] is still alive.

Console.WriteLine("tonsOfObjects[9000] is no longer alive.");

// Print out how many times a generation has been swept.

Console.WriteLine("\nGen 0 has been swept {0} times",

test-only made one explicit request for a garbage collection (via the GC.Collect() method), the CLR

per-formed a number of them in the background

Figure 8-6.Interacting with the CLR garbage collector via System.GC

At this point in the chapter, I hope you feel more comfortable regarding the details of objectlifetime The remainder of this chapter examines the garbage collection process a bit further by

addressing how you can build finalizable objects as well as disposable objects Be very aware that the

following techniques will only be useful if you are building managed classes that maintain internal

unmanaged resources

■ Source Code The SimpleGC project is included under the Chapter 8 subdirectory

www.free-ebooks-download.org

Trang 5

Building Finalizable Objects

In Chapter 6, you learned that the supreme base class of NET, System.Object, defines a virtualmethod named Finalize() The default implementation of this method does nothing whatsoever:

per-tor Rather, the garbage collector will call an object’s Finalize() method (if supported) before

removing the object from memory

■ Note It is illegal to override Finalize()on structure types This makes perfect sense given that structures arevalue types, which are never allocated on the heap to begin with, and therefore are not garbage collected!

Of course, a call to Finalize() will (eventually) occur during a “natural” garbage collection

or when you programmatically force a collection via GC.Collect() In addition, a type’s finalizer

method will automatically be called when the application domain hosting your application is

unloaded from memory Based on your current background in NET, you may know that applicationdomains (or simply AppDomains) are used to host an executable assembly and any necessaryexternal code libraries If you are not familiar with this NET concept, you will be by the time you’vefinished Chapter 17 The short answer is that when your AppDomain is unloaded from memory, theCLR automatically invokes finalizers for every finalizable object created during its lifetime

Now, despite what your developer instincts may tell you, a vast majority of your C# classes will

not require any explicit cleanup logic and will not need a custom finalizer The reason is simple: ifyour types are simply making use of other managed objects, everything will eventually be garbagecollected The only time you would need to design a class that can clean up after itself is when you

are making use of unmanaged resources (such as raw OS file handles, raw unmanaged database

connections, chunks of unmanaged memory, or other unmanaged resources) Under the NET form, unmanaged resources are obtained by directly calling into the API of the operating systemusing Platform Invocation Services (PInvoke) or due to some very elaborate COM interoperabilityscenarios Given this, consider the next rule of garbage collection:

plat-■ Rule The only reason to override Finalize()is if your C# class is making use of unmanaged resources viaPInvoke or complex COM interoperability tasks (typically via various members defined by the System.Runtime.InteropServices.Marshaltype)

Overriding System.Object.Finalize()

In the rare case that you do build a C# class that makes use of unmanaged resources, you will ously wish to ensure that the underlying memory is released in a predictable manner Assume youhave created a new C# Console Application named SimpleFinalize and inserted a class namedMyResourceWrapper that makes use of an unmanaged resource (whatever that may be) and you wish

obvi-www.free-ebooks-download.org

Trang 6

to override Finalize() The odd thing about doing so in C# is that you cannot do so using the

expected override keyword:

public class MyResourceWrapper

this alternative form of overriding a virtual method is that when the C# compiler processes the

finalizer syntax, it will automatically add a good deal of required infrastructure within the implicitly

overridden Finalize() method (shown in just a moment)

C# finalizers look very similar to a constructor in that they are named identically to the classthey are defined within In addition, finalizers are prefixed with a tilde symbol (~) Unlike a con-

structor, however, finalizers never take an access modifier (they are implicitly protected), never take

parameters and cannot be overloaded (only one finalizer per class)

Here is a custom finalizer for MyResourceWrapper that will issue a system beep when invoked

Obviously this is only for instructional purposes A real-world finalizer would do nothing more than

free any unmanaged resources and would not interact with other managed objects, even those

ref-erenced by the current object, as you cannot assume they are still alive at the point the garbage

collector invokes your Finalize() method:

// Override System.Object.Finalize() via finalizer syntax.

class MyResourceWrapper

{

~MyResourceWrapper()

{

// Clean up unmanaged resources here.

// Beep when destroyed (testing purposes only!)

Finalize() method are placed within a try block (see Chapter 7) The related finally block ensures

that your base classes’ Finalize() method will always execute, regardless of any exceptions

encountered within the try scope:

.method family hidebysig virtual instance void

Finalize() cil managed

void [mscorlib]System.Console::Beep(int32, int32)

IL_000f: nopIL_0010: nopIL_0011: leave.s IL_001b} // end try

www.free-ebooks-download.org

Trang 7

IL_001b: nop

IL_001c: ret

} // end of method MyResourceWrapper::Finalize

If you were to now test the MyResourceWrapper type, you would find that a system beep occurswhen the application terminates, given that the CLR will automatically invoke finalizers uponAppDomain shutdown:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Finalizers *****\n");

Console.WriteLine("Hit the return key to shut down this app");

Console.WriteLine("and force the GC to invoke Finalize()");

Console.WriteLine("for finalizable objects created in this AppDomain.");

Console.ReadLine();

MyResourceWrapper rw = new MyResourceWrapper();

}

■ Source Code The SimpleFinalize project is included under the Chapter 8 subdirectory

Detailing the Finalization Process

Not to beat a dead horse, but always remember that the role of the Finalize() method is to ensurethat a NET object can clean up unmanaged resources when garbage collected Thus, if you arebuilding a type that does not make use of unmanaged entities (by far the most common case), final-ization is of little use In fact, if at all possible, you should design your types to avoid supporting aFinalize() method for the very simple reason that finalization takes time

When you allocate an object onto the managed heap, the runtime automatically determineswhether your object supports a custom Finalize() method If so, the object is marked as

finalizable, and a pointer to this object is stored on an internal queue named the finalization queue.

The finalization queue is a table maintained by the garbage collector that points to each and everyobject that must be finalized before it is removed from the heap

When the garbage collector determines it is time to free an object from memory, it examineseach entry on the finalization queue and copies the object off the heap to yet another managed

structure termed the finalization reachable table (often abbreviated as freachable, and pronounced

“eff-reachable”) At this point, a separate thread is spawned to invoke the Finalize() method for

each object on the freachable table at the next garbage collection Given this, it will take at the very least two garbage collections to truly finalize an object.

The bottom line is that while finalization of an object does ensure an object can clean upunmanaged resources, it is still nondeterministic in nature, and due to the extra behind-the-curtains processing, considerably slower

www.free-ebooks-download.org

Trang 8

Building Disposable Objects

As you have seen, finalizers can be used to release unmanaged resources when the garbage

collec-tor kicks in However, given that many unmanaged objects are “precious items” (such as database orfile handles), it may be valuable to release them as soon as possible instead of relying on a garbage

collection to occur As an alternative to overriding Finalize(), your class could implement the

IDisposable interface, which defines a single method named Dispose():

public interface IDisposable

{

void Dispose();

}

If you are new to interface-based programming, Chapter 9 will take you through the details In

a nutshell, an interface as a collection of abstract members a class or structure may support When

you do support the IDisposable interface, the assumption is that when the object user is finished

using the object, it manually calls Dispose() before allowing the object reference to drop out of

scope In this way, an object can perform any necessary cleanup of unmanaged resources without

incurring the hit of being placed on the finalization queue and without waiting for the garbage

col-lector to trigger the class’s finalization logic

■ Note Structures and class types can both implement IDisposable(unlike overriding Finalize(), which is

reserved for class types), as the object user (not the garbage collector) invokes the Dispose()method

To illustrate the use of this interface, create a new C# Console Application named Dispose Here is an updated MyResourceWrapper class that now implements IDisposable, rather

Simple-than overriding System.Object.Finalize():

// Implementing IDisposable.

public class MyResourceWrapper : IDisposable

{

// The object user should call this method

// when they finish with the object.

public void Dispose()

{

// Clean up unmanaged resources

// Dispose other contained disposable objects

// Just for a test.

Finalize(), it is perfectly safe to communicate with other managed objects within a Dispose()

method The reason is simple: the garbage collector has no clue about the IDisposable interface

and will never call Dispose() Therefore, when the object user calls this method, the object is still

living a productive life on the managed heap and has access to all other heap-allocated objects

The calling logic is straightforward:

www.free-ebooks-download.org

Trang 9

public class Program

{

static void Main()

{

Console.WriteLine("***** Fun with Dispose *****\n");

// Create a disposable object and call Dispose() // to free any internal resources.

MyResourceWrapper rw = new MyResourceWrapper();

public class Program

{

static void Main()

{

Console.WriteLine("***** Fun with Dispose *****\n");

MyResourceWrapper rw = new MyResourceWrapper();

if (rw is IDisposable)rw.Dispose();

Console.ReadLine();

}

}

This example exposes yet another rule of working with garbage-collected types

■ Rule Always call Dispose()on any object you directly create if the object supports IDisposable Theassumption you should make is that if the class designer chose to support the Dispose()method, the type hassome cleanup to perform

There is one caveat to the previous rule A number of types in the base class libraries that doimplement the IDisposable interface provide a (somewhat confusing) alias to the Dispose()method, in an attempt to make the disposal-centric method sound more natural for the definingtype By way of an example, while the System.IO.FileStream class implements IDisposable (and

therefore supports a Dispose() method), it also defines a Close() method that is used for the same

purpose:

// Assume you have imported

// the System.IO namespace

static void DisposeFileStream()

{

FileStream fs = new FileStream("myFile.txt", FileMode.OpenOrCreate);

// Confusing, to say the least!

// These method calls do the same thing!

fs.Close();

fs.Dispose();

}

www.free-ebooks-download.org

Trang 10

While it does feel more natural to “close” a file rather than “dispose” of one, you may agree thatthis doubling up of disposal-centric methods is confusing For the few types that do provide an

alias, just remember that if a type implements IDisposable, calling Dispose() is always a correct

course of action

Reusing the C# using Keyword

When you are handling a managed object that implements IDisposable, it will be quite common to

make use of structured exception handling to ensure the type’s Dispose() method is called in the

event of a runtime exception:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Dispose *****\n");

MyResourceWrapper rw = new MyResourceWrapper ();

Console.WriteLine("***** Fun with Dispose *****\n");

// Dispose() is called automatically when the

// using scope exits.

using(MyResourceWrapper rw = new MyResourceWrapper())

Trang 11

} // end of method Program::Main

■ Note If you attempt to “use” an object that does not implement IDisposable, you will receive a compilererror

While this syntax does remove the need to manually wrap disposable objects withintry/finally logic, the C# using keyword unfortunately now has a double meaning (specifyingnamespaces and invoking a Dispose() method) Nevertheless, when you are working with NETtypes that support the IDisposable interface, this syntactical construct will ensure that the object

“being used” will automatically have its Dispose() method called once the using block has exited

Also, be aware that it is possible to declare multiple objects of the same type within a using

scope As you would expect, the compiler will inject code to call Dispose() on each declared object:static void Main(string[] args)

{

Console.WriteLine("***** Fun with Dispose *****\n");

// Use a comma-delimited list to declare multiple objects to dispose.

using(MyResourceWrapper rw = new MyResourceWrapper(),

rw2 = new MyResourceWrapper()){

// Use rw and rw2 objects

}

}

■ Source Code The SimpleDispose project is included under the Chapter 8 subdirectory

Building Finalizable and Disposable Types

At this point, we have seen two different approaches to construct a class that cleans up internalunmanaged resources On the one hand, we could override System.Object.Finalize() Using thistechnique, we have the peace of mind that comes with knowing the object cleans itself up whengarbage collected (whenever that may be) without the need for user interaction On the other hand,

we could implement IDisposable to provide a way for the object user to clean up the object as soon

as it is finished However, if the caller forgets to call Dispose(), the unmanaged resources may beheld in memory indefinitely

As you might suspect, it is possible to blend both techniques into a single class definition Bydoing so, you gain the best of both models If the object user does remember to call Dispose(), youcan inform the garbage collector to bypass the finalization process by calling GC.SuppressFinalize()

If the object user forgets to call Dispose(), the object will eventually be finalized and have a chance

to free up the internal resources The good news is that the object’s internal unmanaged resourceswill be freed one way or another

Here is the next iteration of MyResourceWrapper, which is now finalizable and disposable,defined in a C# Console Application named FinalizableDisposableClass:

www.free-ebooks-download.org

Trang 12

// A sophisticated resource wrapper.

public class MyResourceWrapper : IDisposable

{

// The garbage collector will call this method if the

// object user forgets to call Dispose().

~ MyResourceWrapper()

{

// Clean up any internal unmanaged resources.

// Do **not** call Dispose() on any managed objects.

// Clean up unmanaged resources here.

// Call Dispose() on other contained disposable objects.

// No need to finalize if user called Dispose(), // so suppress finalization.

lected, given that the unmanaged resources have already been freed via the Dispose() logic

A Formalized Disposal Pattern

The current implementation of MyResourceWrapper does work fairly well; however, we are left with a

few minor drawbacks First, the Finalize() and Dispose() methods each have to clean up the same

unmanaged resources This could result in duplicate code, which can easily become a nightmare to

maintain Ideally, you would define a private helper function that is called by either method

Next, you would like to make sure that the Finalize() method does not attempt to dispose ofany managed objects, while the Dispose() method should do so Finally, you would also like to

make sure that the object user can safely call Dispose() multiple times without error Currently, our

Dispose() method has no such safeguards

To address these design issues, Microsoft has defined a formal, prim-and-proper disposal tern that strikes a balance between robustness, maintainability, and performance Here is the final

pat-(and annotated) version of MyResourceWrapper, which makes use of this official pattern:

public class MyResourceWrapper : IDisposable

{

// Used to determine if Dispose()

// has already been called.

private bool disposed = false;

public void Dispose()

{

// Call our helper method

// Specifying "true" signifies that // the object user triggered the cleanup.

CleanUp(true);

www.free-ebooks-download.org

Trang 13

// Now suppress finalization.

// If disposing equals true, dispose all // managed resources.

if (disposing){

// Dispose managed resources.

}

// Clean up unmanaged resources here.

}disposed = true;

}

~MyResourceWrapper()

{

// Call our helper method.

// Specifying "false" signifies that // the GC triggered the cleanup.

therefore we should clean up all managed and unmanaged resources However, when the garbage

collector initiates the cleanup, we specify false when calling CleanUp() to ensure that internal

dis-posable objects are not disposed (as we can’t assume they are still in memory!) Last but not least,

our bool member variable (disposed) is set to true before exiting CleanUp() to ensure that Dispose()can be called numerous times without error

To test our final iteration of MyResourceWrapper, add a call to Console.Beep() within the scope

of your finalizer:

~MyResourceWrapper()

{

Console.Beep();

// Call our helper method

// Specifying "false" signifies that

// the GC triggered the cleanup

CleanUp(false);

}

Next, update Main() as follows:

static void Main(string[] args)

{

Console.WriteLine("***** Dispose() / Destructor Combo Platter *****");

// Call Dispose() manually, this will not call the finalizer.

MyResourceWrapper rw = new MyResourceWrapper();

rw.Dispose();

www.free-ebooks-download.org

Trang 14

// Don't call Dispose(), this will trigger the finalizer

// and cause a beep.

MyResourceWrapper rw2 = new MyResourceWrapper();

}

Notice that we are explicitly calling Dispose() on the rw object, therefore the destructor call issuppressed However, we have “forgotten” to call Dispose() on the rw2 object, and therefore when

the application terminates, we hear a single beep If you were to comment out the call to Dispose()

on the rw object, you would hear two beeps

■ Source Code The FinalizableDisposableClass project is included under the Chapter 8 subdirectory

That wraps up our investigation of how the CLR is managing your objects via garbage tion While there are additional (fairly esoteric) details regarding the collection process I have not

collec-examined here (such as weak references and object resurrection), you are certainly in a perfect

position for further exploration on your own terms

Summary

The point of this chapter was to demystify the garbage collection process As you have seen, the

garbage collector will only run when it is unable to acquire the necessary memory from the

man-aged heap (or when a given AppDomain unloads from memory) When a collection does occur, you

can rest assured that Microsoft’s collection algorithm has been optimized by the use of object

gen-erations, secondary threads for the purpose of object finalization, and a managed heap dedicated tohost large objects

This chapter also illustrated how to programmatically interact with the garbage collector usingthe System.GC class type As mentioned, the only time when you will really need to do so is when

you are building finalizable or disposable class types that operate upon unmanaged resources

Recall that finalizable types are classes that have overridden the virtual System.Object

Finalize() method to clean up unmanaged resources at the time of garbage collection Disposable

objects, on the other hand, are classes (or structures) that implement the IDisposable interface,

which should be called by the object user when it is finished using said object Finally, you learned

about an official “disposal” pattern that blends both approaches.www.free-ebooks-download.org

Trang 15

www.free-ebooks-download.org

Trang 16

Advanced C# Programming Constructs

P A R T 3

www.free-ebooks-download.org

Trang 17

www.free-ebooks-download.org

Trang 18

Working with Interfaces

This chapter builds on your current understanding of object-oriented development by examining

the topic of interface-based programming Here you learn how to define and implement interfaces,

and come to understand the benefits of building types that support “multiple behaviors.” Along the

way, a number of related topics are also discussed, such as obtaining interface references, explicit

interface implementation, and the construction of interface hierarchies

The remainder of this chapter is spent examining a number of standard interfaces definedwithin the NET base class libraries As you will see, your custom types are free to implement these

predefined interfaces to support a number of advanced behaviors such as object cloning, object

enumeration, and object sorting We wrap up the chapter by examining how interface types can be

used to establish a callback mechanism, allowing two objects in memory to communicate in a

bidirectional manner

Understanding Interface Types

To begin this chapter, allow me to provide a formal definition of the interface type An interface is

nothing more than a named set of abstract members Recall from Chapter 6 that abstract methods

are pure protocol in that they do not provide a default implementation The specific members

defined by an interface depend on the exact behavior it is modeling Yes, it’s true An interface

expresses a behavior that a given class or structure may choose to implement Furthermore, as you

will see in this chapter, a class (or structure) can support as many interfaces as necessary, thereby

supporting (in essence) multiple behaviors

As you might guess, the NET base class libraries ship with hundreds of predefined interfacetypes that are implemented by various classes and structures For example, as you will see in Chap-

ter 22, ADO.NET ships with multiple data providers that allow you to communicate with a particulardatabase management system Thus, unlike COM-based ADO, under ADO.NET we have numerous

connection objects we may choose between (SqlConnection, OracleConnection, OdbcConnection,

etc.)

Regardless of the fact that each connection object has a unique name, is defined within a ferent namespace, and (in some cases) is bundled within a different assembly, all connection

dif-objects implement a common interface named IDbConnection:

// The IDbConnection interface defines a common

// set of members supported by all connection objects.

public interface IDbConnection : IDisposable

{

// Methods

IDbTransaction BeginTransaction();

IDbTransaction BeginTransaction(IsolationLevel il);

void ChangeDatabase(string databaseName);

C H A P T E R 9

www.free-ebooks-download.org

Trang 19

IDbCommand CreateCommand();

void Open();

// Properties

string ConnectionString { get; set;}

int ConnectionTimeout { get; }

string Database { get; }

ConnectionState State { get; }

in its own unique manner

Another example: the System.Windows.Forms namespace defines a class named Control, which

is a base class to a number of Windows Forms UI widgets (DataGridView, Label, StatusBar, TreeView,etc.) The Control class implements an interface named IDropTarget, which defines basic drag-and-drop functionality:

public interface IDropTarget

{

// Methods

void OnDragDrop(DragEventArgs e);

void OnDragEnter(DragEventArgs e);

void OnDragLeave(EventArgs e);

void OnDragOver(DragEventArgs e);

}

Based on this interface, we can now correctly assume that any class that extends System.Windows.Forms.Control supports four subroutines named OnDragDrop(), OnDragEnter(),

OnDragLeave(), and OnDragOver()

As you work through the remainder of this text, you will be exposed to dozens of interfaces thatship with the NET base class libraries As you will see, these interfaces can be implemented on yourown custom classes and structures to define types that integrate tightly within the framework

Contrasting Interface Types to Abstract Base Classes

Given your work in Chapter 6, the interface type may seem very similar to an abstract base class

Recall that when a class is marked as abstract, it may define any number of abstract members to

provide a polymorphic interface to all derived types However, even when a class type does define aset of abstract members, it is also free to define any number of constructors, field data, nonabstract

members (with implementation), and so on Interfaces, on the other hand, only contain abstract

members

The polymorphic interface established by an abstract parent class suffers from one major

limi-tation in that only derived types support the members defined by the abstract parent However, in

larger software systems, it is very common to develop multiple class hierarchies that have no mon parent beyond System.Object Given that abstract members in an abstract base class only

com-www.free-ebooks-download.org

Trang 20

apply to derived types, we have no way to configure types in different hierarchies to support the

same polymorphic interface By way of an illustrative example, assume you have defined the

follow-ing abstract class:

abstract class CloneableType

{

// Only derived types can support this

// "polymorphic interface." Classes in other

// heirarchies have no access to this abstract

to gain this polymorphic interface As you would guess, interface types come to the rescue Once an

interface has been defined, it can be implemented by any type, in any hierarchy, within any

name-spaces or any assembly (written in any NET programming language) Given this, interfaces are

highly polymorphic Consider the standard NET interface named ICloneable defined in the System

namespace This interface defines a single method named Clone():

public interface ICloneable

System.OperatingSystem, System.String, etc.) all implement this interface Although these types

have no common parent (other than System.Object), we can treat them polymorphically via the

ICloneable interface type

For example, if we had a method named CloneMe() that took an ICloneable interface ter, we could pass this method any object that implements said interface Consider the following

parame-simple Program class defined within a Console Application named ICloneableExample:

class Program

{

static void Main(string[] args)

{

Console.WriteLine("***** A First Look at Interfaces *****\n");

// All of these types support the ICloneable interface.

string myStr = "Hello";

OperatingSystem unixOS = new OperatingSystem(PlatformID.Unix, new Version());

System.Data.SqlClient.SqlConnection sqlCnn =new System.Data.SqlClient.SqlConnection();

// Therefore, they can all be passed into a method taking ICloneable.

Trang 21

object theClone = c.Clone();

Console.WriteLine("Your clone is a: {0}",theClone.GetType().Name);

}

}

When you run this application, you will find the full name of each class print out to the console,via the GetType() method you inherit from System.Object (Chapter 16 will provide full coverage ofthis method and NET reflection services)

■ Source Code The ICloneableExample project is located under the Chapter 9 subdirectory

Another limitation of traditional abstract base classes is that each and every derived type must

contend with the set of abstract members and provide an implementation To see this problem,recall the shapes hierarchy we defined in Chapter 6 Assume we defined a new abstract method inthe Shape base class named GetNumberOfPoints(), which allows derived types to return the number

of points required to render the shape:

abstract class Shape

{

// Every derived class must now support this method!

public abstract byte GetNumberOfPoints();

}

Clearly, the only type that has any points in the first place is Hexagon However, with this

update, every derived type (Circle, Hexagon, and ThreeDCircle) must now provide a concrete

imple-mentation of this function even if it makes no sense to do so

Again, the interface type provides a solution If we were to define an interface that representsthe behavior of “having points,” we could simply plug it into the Hexagon type, leaving Circle andThreeDCircle untouched

Defining Custom Interfaces

Now that you better understand the overall role of interface types, let’s see an example of definingcustom interfaces To begin, create a brand-new Console Application named CustomInterface

Using the Project ➤ Add Existing Item menu option, insert the files containing your shape type

defi-nitions (MyShapes.cs and Shape.cs in the book’s solution code) created back in Chapter 6 during theShapes example Once you have done so, rename the namespace that defines your shape-centrictypes to CustomInterface (simply to avoid having to import namespace definitions within your newproject):

namespace CustomInterface

{

// Your previous shape types defined here

}

Now, insert a new interface into your project named IPointy using the Project ➤ Add New Item

menu option, as shown in Figure 9-1

www.free-ebooks-download.org

Trang 22

Figure 9-1.Interfaces, like classes, can be defined in any *.cs file.

At a syntactic level, an interface is defined using the C# interface keyword Unlike other NETtypes, interfaces never specify a base class (not even System.Object) and their members never spec-

ify an access modifier (as all interface members are implicitly public and abstract) To get the ball

rolling, here is a custom interface defined in C#:

// This interface defines the behavior of "having points."

public interface IPointy

{

// Implicitly public and abstract.

byte GetNumberOfPoints();

}

Notice that when you define interface members, you do not define an implementation scope

for the member in question Interfaces are pure protocol, and therefore never define an

implemen-tation (that is up to the supporting class or structure) Therefore, the following version of IPointy

would result in various compiler errors:

// Ack! Errors abound!

public interface IPointy

{

// Error! Interfaces cannot have fields!

public int numbOfPoints;

// Error! Interfaces do not have constructors!

public IPointy() { numbOfPoints = 0;};

// Error! Interfaces don't provide an implementation!

byte GetNumberOfPoints() { return numbOfPoints; }

}

www.free-ebooks-download.org

Trang 23

In any case, this initial IPointy interface defines a single method However, NET interfacetypes are also able to define any number of property prototypes For example, you could create theIPointy interface to use a read-only property rather than a traditional accessor method:

// The pointy behavior as a read-only property.

public interface IPointy

{

// A read-write property in an interface would look like

// retVal PropName { get; set; }

// while a write-only property in an interface would be

// retVal PropName { set; }

byte Points{ get; }

}

■ Note Interface types can also contain event (see Chapter 11) and indexer (see Chapter 12) definitions

Do understand that interface types are quite useless on their own, as they are nothing morethan a named collection of abstract members For example, you cannot allocate interface types asyou would a class or structure:

// Ack! Illegal to allocate interface types.

static void Main(string[] args)

Implementing an Interface

When a class (or structure) chooses to extend its functionality by supporting interface types, it does

so using a comma-delimited list in the type definition Be aware that the direct base class must bethe first item listed after the colon operator When your class type derives directly from System.Object, you are free to simply list the interface(s) supported by the class, as the C# compiler willextend your types from System.Object if you do not say otherwise On a related note, given thatstructures always derive from System.ValueType (see Chapter 4 for full details), simply list eachinterface directly after the structure definition Ponder the following examples:

// This class derives from System.Object and

// implements a single interface.

public class Pencil : IPointy

{ }

// This class also derives from System.Object

// and implements a single interface.

public class SwitchBlade : object, IPointy

{ }

www.free-ebooks-download.org

Trang 24

// This class derives from a custom base class

// and implements a single interface.

public class Fork : Utensil, IPointy

{ }

// This struct implicitly derives from System.ValueType and

// implements two interfaces.

public struct Arrow : IClonable, IPointy

{ }

Understand that implementing an interface is an all-or-nothing proposition The supportingtype is not able to selectively choose which members it will implement Given that the IPointy

interface defines a single read-only property, this is not too much of a burden

However, if you are implementing an interface that defines ten members (such as theIDbConnection interface seen earlier), the type is now responsible for fleshing out the details of

all ten abstract entities

For this example, insert a new class type named Triangle which “is-a” Shape and supportsIPointy:

// New Shape derived class named Triangle.

public class Triangle : Shape, IPointy

{

public Triangle() { }

public Triangle(string name) : base(name) { }

public override void Draw()

{ Console.WriteLine("Drawing {0} the Triangle", PetName); }

// IPointy Implementation.

public byte Points

{

get { return 3; }}

}

Now, update your existing Hexagon type to also support the IPointy interface type:

// Hexagon now implements IPointy.

public class Hexagon : Shape, IPointy

{

public Hexagon(){ }

public Hexagon(string name) : base(name){ }

public override void Draw()

{ Console.WriteLine("Drawing {0} the Hexagon", PetName); }

// IPointy Implementation.

public byte Points

{

get { return 6; }}

Trang 25

Figure 9-2.The shapes hierarchy (now with interfaces)

■ Note To display or hide interface names on the class designer, right-click on the interface icon and selectCollapse or Expand

Invoking Interface Members at the Object Level

Now that you have a set of types that support the IPointy interface, the next question is how youinteract with the new functionality The most straightforward way to interact with functionalitysupplied by a given interface is to invoke the methods directly from the object level (provided theinterface members are not implemented explicitly; more details later in the section “ResolvingName Clashes via Explicit Interface Implementation”) For example, consider the following Main()method:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Interfaces *****\n");

// Call Points property defined by IPointy.

Hexagon hex = new Hexagon();

One way to determine at runtime whether a type supports a specific interface is to makeuse of an explicit cast If the type does not support the requested interface, you receive an

InvalidCastException To handle this possibility gracefully, make use of structured exceptionhandling, for example:

www.free-ebooks-download.org

Trang 26

static void Main(string[] args)

{

// Catch a possible InvalidCastException.

Circle c = new Circle("Lisa");

IPointy itfPt = null;

deter-see two ways of doing so

Obtaining Interface References: The as Keyword

The second way you can determine whether a given type supports an interface is to make use of the

as keyword, which was first introduced in Chapter 6 If the object can be treated as the specified

interface, you are returned a reference to the interface in question If not, you receive a null

refer-ence Therefore, be sure to check against a null value before proceeding:

static void Main(string[] args)

{

// Can we treat hex2 as IPointy?

Hexagon hex2 = new Hexagon("Peter");

IPointy itfPt2 = hex2 as IPointy;

Obtaining Interface References: The is Keyword

You may also check for an implemented interface using the is keyword (also first seen in Chapter 6)

If the object in question is not compatible with the specified interface, you are returned the value

false On the other hand, if the type is compatible with the interface in question, you can safely call

the members without needing to make use of try/catch logic

To illustrate, assume we have an array of Shape types containing some members that ment IPointy Notice how we are able to determine which item in the array supports this interface

imple-using the is keyword, as shown in this retrofitted Main() method:

www.free-ebooks-download.org

Trang 27

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Interfaces *****\n");

// Make an array of Shapes.

Shape[] s = { new Hexagon(), new Circle(), new Triangle("Joe"),

new Circle("JoJo")} ;for(int i = 0; i < s.Length; i++)

elseConsole.WriteLine("-> {0}\'s not pointy!", s[i].PetName);

Console.WriteLine();

}

Console.ReadLine();

}

The output follows in Figure 9-3

Figure 9-3.Dynamically determining implemented interfaces

Interfaces As Parameters

Given that interfaces are valid NET types, you may construct methods that take interfaces asparameters as illustrated by the CloneMe() method earlier in this chapter For the current example,assume you have defined another interface named IDraw3D:

// Models the ability to render a type in stunning 3D.

public interface IDraw3D

{

void Draw3D();

}

www.free-ebooks-download.org

Trang 28

Next, assume that two of your three shapes (Circle and Hexagon) have been configured to port this new behavior:

sup-// Circle supports IDraw3D.

public class Circle : Shape, IDraw3D

// Hexagon supports IPointy and IDraw3D.

public class Hexagon : Shape, IPointy, IDraw3D

Figure 9-4 presents the updated Visual Studio 2008 class diagram

Figure 9-4.The updated shapes hierarchy

If you now define a method taking an IDraw3D interface as a parameter, you are able to tively send in any object implementing IDraw3D (if you attempt to pass in a type not supporting the

effec-necessary interface, you receive a compile-time error) Consider the following method defined

within your Program type:

// I'll draw anyone supporting IDraw3D.

static void DrawIn3D(IDraw3D itf3d)

Trang 29

static void Main()

{

Console.WriteLine("***** Fun with Interfaces *****\n");

Shape[] s = { new Hexagon(), new Circle(),

new Triangle(), new Circle("JoJo") } ;for(int i = 0; i < s.Length; i++)

{

// Can I draw you in 3D?

if(s[i] is IDraw3D)DrawIn3D((IDraw3D)s[i]);

}

}

Notice that the Triangle type is never drawn in 3D, as it is not IDraw3D-compatible (see Figure 9-5)

Figure 9-5.Interfaces as parameters

Interfaces As Return Values

Interfaces can also be used as method return values For example, you could write a method thattakes any System.Object, checks for IPointy compatibility, and returns a reference to the extractedinterface (if supported):

// This method tests for IPointy compatibility and,

// if able, returns an interface reference.

static IPointy ExtractPointyness(object o)

Trang 30

static void Main(string[] args)

Arrays of Interface Types

Recall that the same interface can be implemented by numerous types, even if they are not within

the same class hierarchy and do not have a common parent class beyond System.Object This can

yield some very powerful programming constructs For example, assume that you have developed

three new class types within your current project modeling kitchen utensils (via Knife and Fork

classes) and another modeling gardening equipment (à la PitchFork) Consider Figure 9-6

Figure 9-6.Recall that interfaces can be “plugged into” any type in any part of a class hierarchy.

If you did define the PitchFork, Fork, and Knife types, you could now define an array ofIPointy-compatible objects Given that these members all support the same interface, you are able

to iterate through the array and treat each item as an IPointy-compatible object, regardless of the

overall diversity of the class hierarchies:

static void Main(string[] args)

{

// This array can only contain types that

// implement the IPointy interface.

IPointy[] myPointyObjects = {new Hexagon(), new Knife(),

new Triangle(), new Fork(), new PitchFork()};

www.free-ebooks-download.org

Trang 31

foreach(IPointy i in myPointyObjects)

Console.WriteLine("Object has {0} points.", i.Points);

Console.ReadLine();

}

■ Source Code The CustomInterface project is located under the Chapter 9 subdirectory

Implementing Interfaces Using Visual Studio 2008

Although interface-based programming is a very powerful programming technique, implementinginterfaces may entail a healthy amount of typing Given that interfaces are a named set of abstract

members, you will be required to type in the definition and implementation for each interface method on each type that supports the behavior.

As you would hope, Visual Studio 2008 does support various tools that make the task of menting interfaces less burdensome By way of a simple test, insert a final class into your currentproject named PointyTestClass When you implement IPointy (or any interface for that matter) on

imple-a type, you might himple-ave noticed thimple-at when you complete typing the interfimple-ace’s nimple-ame (or when youposition the mouse cursor on the interface name in the code window), the first letter is underlined(formally termed a “smart tag”) When you click the smart tag, you will be presented a drop-downlist that allows you to implement the interface (see Figure 9-7)

Figure 9-7.Implementing interfaces using Visual Studio 2008

Notice you are presented with two options, the second of which (explicit interface tation) will be examined in the next section For the time being, once you select the first option, youwill see that Visual Studio 2008 has built generated stub code (within a named code region) for you

implemen-to update (note that the default implementation throws a System.Exception, which can obviously

www.free-ebooks-download.org

Trang 32

get { throw new Exception("The method or operation is not implemented."); }}

#endregion}

}

■ Note Visual Studio 2008 also supports an extract interface refactoring, available from the Refactoring menu

This allows you to pull out a new interface definition from an existing class definition See my MSDN article

“Refactoring C# Code Using Visual Studio 2005” (the same holds true for Visual Studio 2008) for further details

Resolving Name Clashes via Explicit Interface

Implementation

As shown earlier in this chapter, a single class or structure can implement any number of interfaces

Given this, there is always a possibility that you may implement interfaces that contain identically

named members, and therefore have a name clash to contend with To illustrate various manners in

which you can resolve this issue, create a new Console Application named InterfaceNameClash

Now design three custom interfaces that represent various locations to which an implementing

type could render its output:

// Draw image to a Form.

public interface IDrawToForm

{

void Draw();

}

// Draw to buffer in memory.

public interface IDrawToMemory

{

void Draw();

}

// Render to the printer.

public interface IDrawToPrinter

// Shared drawing logic.

Console.WriteLine("Drawing the Octagon ");

}

}

www.free-ebooks-download.org

Trang 33

Although the code compiles cleanly, you may agree we do have a possible problem Simply put,providing a single implementation of the Draw() method does not allow us to take unique courses ofaction based on which interface is obtained from an Octagon object For example, the followingcode will invoke the same Draw() method, regardless of which interface we obtain:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Interface Name Clashes *****\n");

// All of these invocations call the

// same Draw() method!

Octagon oct = new Octagon();

using explicit interface implementation syntax Consider the following update to the Octagon type:

class Octagon : IDrawToForm, IDrawToMemory, IDrawToPrinter

imple-// Error! No access modifer!

public void IDrawToForm.Draw()

{

Console.WriteLine("Drawing to form ");

}

www.free-ebooks-download.org

Trang 34

Because explicitly implemented members are always implicitly private, these members are nolonger available from the object level In fact, if you were to apply the dot operator to an Octagon

type, you will find that IntelliSense will not show you any of the Draw() members (see Figure 9-8)

Figure 9-8.Explicitly implemented interface members are not exposed from the object level.

As expected, you must make use of explicit casting to access the required functionality Forexample:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Interface Name Clashes *****\n");

Octagon oct = new Octagon();

// We now must use casting to access the Draw()

// members.

IDrawToForm itfForm = (IDrawToForm)oct;

itfForm.Draw();

// Shorthand notation if you don't need

// the interface variable for later use.

In this way, when the object user applies the dot operator, he or she will only see a subset of the

type’s overall functionality However, those who require the more advanced behaviors can extract

out the desired interface via an explicit cast

www.free-ebooks-download.org

Trang 35

■ Source Code The InterfaceNameClash project is located under the Chapter 9 subdirectory.

Designing Interface Hierarchies

Interfaces can be arranged into an interface hierarchy Like a class hierarchy, when an interfaceextends an existing interface, it inherits the abstract members defined by the parent type(s) Ofcourse, unlike class-based inheritance, derived interfaces never inherit true implementation.Rather, a derived interface simply extends its own definition with additional abstract members.Interface hierarchies can be useful when you wish to extend the functionality of an existinginterface without breaking existing code bases To illustrate, create a new Console Applicationnamed InterfaceHierarchy Now, let’s redesign the previous set of rendering-centric interfaces(from the InterfaceNameClash example) such that IDrawable is the root of the family tree:

public interface IDrawable

public interface IPrintable : IDrawable

public class SuperShape : IRenderToMemory

Trang 36

Console.WriteLine("***** The SuperShape *****");

// Call from object level.

SuperShape myShape = new SuperShape();

myShape.Draw();

// Get IPrintable explicitly.

// (and IDrawable implicitly!)

■ Source Code The InterfaceHierarchy project is located under the Chapter 9 subdirectory

Multiple Inheritance with Interface Types

Unlike class types, it is possible for a single interface to extend multiple base interfaces This allows

us to design some very powerful and flexible abstractions Create a new Console Application project

named MIInterfaceHierarchy Here is a brand-new collection of interfaces that model various

ren-dering and shape-centric abstractions Notice that the IShape interface is extending both IDrawable

and IPrintable:

// Multiple inheritance for interface types is a-okay.

public interface IDrawable

// Multiple interface inheritance OK!

public interface IShape : IDrawable, IPrintable

Trang 37

Figure 9-9.Unlike classes, interfaces can extend multiple interface types.

Now, the million dollar question is, if we have a class supporting IShape, how many methodswill it be required to implement? The answer: it depends If we wish to provide a simple implemen-tation of the Draw() method, we only need to provide three members, as shown in the followingRectangle type:

class Rectangle : IShape

class Square : IShape

Trang 38

So at this point, you hopefully feel more comfortable with the process of defining and menting custom interfaces using the syntax of C# To be honest, interface-based programming can

imple-take awhile to get comfortable with, so if you are in fact still scratching your head just a bit, this is a

perfectly normal reaction

Do be aware, however, that interfaces are a fundamental aspect of the NET Framework

Regardless of the type of application you are developing (web-based, desktop GUIs, data access

libraries, etc.), working with interfaces will be part of the process To summarize the story thus far,

remember that interfaces can be extremely useful when

• You have a single hierarchy where only a subset of the derived types support a commonbehavior

• You need to model a common behavior that is found across multiple hierarchies with nocommon parent class beyond System.Object

Now that you have drilled into the specifics of building and implementing custom interfaces,the remainder of the chapter examines a number of predefined interfaces contained within the

.NET base class libraries

■ Source Code The MIInterfaceHierarchy project is located under the Chapter 9 subdirectory

Building Enumerable Types (IEnumerable and

IEnumerator)

To begin examining the process of implementing existing NET interfaces, let’s first look at the role

of IEnumerable and IEnumerator Recall that C# supports a keyword named foreach, which allows

you to iterate over the contents of any array type:

// Iterate over an array of items.

While it may seem that only array types can make use of this construct, the truth of the matter

is any type supporting a method named GetEnumerator() can be evaluated by the foreach

con-struct To illustrate, begin by creating a new Console Application project named CustomEnumerator

Next, add the Car.cs and Radio.cs files defined in the SimpleException example of Chapter 7 (via

the Project ➤ Add Existing Item menu option) and update the current class definition with two

new properties (named PetName and Speed) that wrap the existing currSpeed and petName member

variables:

public class Car

{

private int currSpeed;

private string petName;

public int Speed

{

get { return currSpeed; }

www.free-ebooks-download.org

Trang 39

set { currSpeed = value; }}

public string PetName

{

get { return petName; }set { petName = value; }}

}

■ Note You may wish to rename the namespace containing the Carand Radiotypes to CustomEnumerator,simply to avoid having to import the CustomExceptionnamespace within this new project

Now, insert a new class named Garage that stores a set of Car types within a System.Array:

// Garage contains a set of Car objects.

public class Garage

{

private Car[] carArray = new Car[4];

// Fill with some Car objects upon startup.

public Garage()

{

carArray[0] = new Car("Rusty", 30);

carArray[1] = new Car("Clunker", 55);

carArray[2] = new Car("Zippy", 30);

carArray[3] = new Car("Fred", 30);

}

}

Ideally, it would be convenient to iterate over the Garage object’s subitems using the C# foreachconstruct, just like an array of data values:

// This seems reasonable

public class Program

{

static void Main(string[] args)

{

Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****\n");

Garage carLot = new Garage();

// Hand over each car in the collection?

foreach (Car c in carLot){

Console.WriteLine("{0} is going {1} MPH",c.PetName, c.Speed);

}Console.ReadLine();

}

}

Sadly, the compiler informs you that the Garage class does not implement a method namedGetEnumerator() This method is formalized by the IEnumerable interface, which is found lurkingwithin the System.Collections namespace Types that support this behavior advertise that they areable to expose contained subitems to the caller (in this example, the foreach keyword itself ):

www.free-ebooks-download.org

Trang 40

// This interface informs the caller

// that the object's subitems can be enumerated.

public interface IEnumerable

caller to traverse the internal objects contained by the IEnumerable-compatible container:

// This interface allows the caller to

// obtain a container's subitems.

public interface IEnumerator

{

bool MoveNext (); // Advance the internal position of the cursor

object Current { get;} // Get the current item (read-only property)

void Reset (); // Reset the cursor before the first member

}

If you wish to update the Garage type to support these interfaces, you could take the long roadand implement each method manually While you are certainly free to provide customized versions

of GetEnumerator(), MoveNext(), Current, and Reset(), there is a simpler way As the System.Array

type (as well as many other types) already implements IEnumerable and IEnumerator, you can

sim-ply delegate the request to the System.Array as follows:

using System.Collections;

public class Garage : IEnumerable

{

// System.Array already implements IEnumerator!

private Car[] carArray = new Car[4];

public Garage()

{

carArray[0] = new Car("FeeFee", 200, 0);

carArray[1] = new Car("Clunker", 90, 0);

carArray[2] = new Car("Zippy", 30, 0);

carArray[3] = new Car("Fred", 30, 0);

Once you have updated your Garage type, you can now safely use the type within the C#

foreach construct Furthermore, given that the GetEnumerator() method has been defined publicly,

the object user could also interact with the IEnumerator type:

// Manually work with IEnumerator.

IEnumerator i = carLot.GetEnumerator();

i.MoveNext();

Car myCar = (Car)i.Current;

Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.Speed);

www.free-ebooks-download.org

Ngày đăng: 12/08/2014, 23:20

TỪ KHÓA LIÊN QUAN