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

Visual CSharp 2005 Recipes (2006) [eng]

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

478 C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

else

{

return true;

}

}

//Implement the IEnumerator.Reset method.

//This method resets the position of the TeamMemberEnumerator

//to the beginning of the TeamMembers collection.

public void Reset()

{

//If underlying Team is invalid, throw exception. if (teamInvalid)

{

throw new InvalidOperationException("Team modified");

}

//Move the currentMember pointer back to the index

//preceding the first element.

currentMember = -1;

}

//An event handler to handle notifications that the underlying

//Team collection has changed.

internal void TeamChange(Team t, EventArgs e)

{

// Signal that the underlying Team is now invalid. teamInvalid = true;

}

}

//A delegate that specifies the signature that all team change event

//handler methods must implement.

public delegate void TeamChangedEventHandler(Team t, EventArgs e);

//An ArrayList to contain the TeamMember objects. private ArrayList teamMembers;

//The event used to notify TeamMemberEnumerators that the Team

//has changed.

public event TeamChangedEventHandler TeamChange;

//Team constructor. public Team()

{

teamMembers = new ArrayList();

}

//Implement the IEnumerable.GetEnumerator method. public IEnumerator GetEnumerator()

{

return new TeamMemberEnumerator(this);

}

//Adds a TeamMember object to the Team.

public void AddMember(TeamMember member)

C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

479

{

teamMembers.Add(member);

// Notify listeners that the list has changed. if (TeamChange != null)

{

TeamChange(this, null);

}

}

}

// A class to demonstrate the use of Team. Public class Recipe13_05

{

public static void Main()

{

//Create a new Team. Team team = new Team();

team.AddMember(new TeamMember("Curly", "Clown")); team.AddMember(new TeamMember("Nick", "Knife Thrower")); team.AddMember(new TeamMember("Nancy", "Strong Man"));

//Enumerate the Team.

Console.Clear();

Console.WriteLine("Enumerate with foreach loop:"); foreach (TeamMember member in team)

{

Console.WriteLine(member.ToString());

}

//Enumerate using a While loop. Console.WriteLine(Environment.NewLine); Console.WriteLine("Enumerate with while loop:"); IEnumerator e = team.GetEnumerator();

while (e.MoveNext())

{

Console.WriteLine(e.Current);

}

//Enumerate the Team and try to add a Team Member.

//(This will cause an exception to be thrown.) Console.WriteLine(Environment.NewLine); Console.WriteLine("Modify while enumerating:"); foreach (TeamMember member in team)

{

Console.WriteLine(member.ToString()); team.AddMember(new TeamMember("Stumpy", "Lion Tamer"));

}

//Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter"); Console.ReadLine();

}

}

}

480 C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

13-6. Implement a Disposable Class

Problem

You need to create a class that references unmanaged resources and provide a mechanism for users of the class to free those unmanaged resources deterministically.

Solution

Implement the System.IDisposable interface, and release the unmanaged resources when client code calls the IDisposable.Dispose method.

How It Works

An unreferenced object continues to exist on the managed heap and consume resources until the garbage collector releases the object and reclaims the resources. The garbage collector will automatically free managed resources (such as memory), but it will not free unmanaged resources (such as file handles and database connections) referenced by managed objects. If an object contains data members that reference unmanaged resources, the object must free those resources explicitly.

One solution is to declare a destructor—or finalizer—for the class (destructor is a C++ term equivalent to the more general .NET term finalizer). Prior to reclaiming the memory consumed by an instance of the class, the garbage collector calls the object’s finalizer. The finalizer can take the necessary steps to release any unmanaged resources. Unfortunately, because the garbage collector uses a single thread to execute all finalizers, use of finalizers can have a detrimental effect on the efficiency of the garbage collection process, which will affect the performance of your application. In addition, you cannot control when the runtime frees unmanaged resources because you cannot call an object’s finalizer directly, and you have only limited control over the activities of the garbage collector using the System.GC class.

As a complementary mechanism to using finalizers, the .NET Framework defines the Dispose pattern as a means to provide deterministic control over when to free unmanaged resources. To implement the Dispose pattern, a class must implement the IDisposable interface, which declares a single method named Dispose. In the Dispose method, you must implement the code necessary to release any unmanaged resources and remove the object from the list of objects eligible for finalization if a finalizer has been defined.

Instances of classes that implement the Dispose pattern are called disposable objects. When code has finished with a disposable object, it calls the object’s Dispose method to free all resources and make it unusable, but still relies on the garbage collector to eventually release the object memory. It’s important to understand that the runtime does not enforce disposal of objects; it’s the responsibility of the client to call the Dispose method. However, because the .NET Framework class library uses the Dispose pattern extensively, C# provides the using statement to simplify the correct use of disposable objects. The following code shows the structure of a using statement:

using (FileStream fileStream = new FileStream("SomeFile.txt", FileMode.Open)) { // Do something with the fileStream object.

}

When the code reaches the end of the block in which the disposable object was declared, the object’s Dispose method is automatically called, even if an exception is raised. Here are some points to consider when implementing the Dispose pattern:

C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

481

Client code should be able to call the Dispose method repeatedly with no adverse effects.

In multithreaded applications, it’s important that only one thread execute the Dispose method concurrently. It’s normally the responsibility of the client code to ensure thread synchronization, although you could decide to implement synchronization within the Dispose method.

The Dispose method should not throw exceptions.

Because the Dispose method does all necessary cleaning up, you do not need to call the object’s finalizer. Your Dispose method should call the GC.SuppressFinalize method to ensure the finalizer is not called during garbage collection.

Implement a finalizer that calls the unmanaged cleanup part of your Dispose method as

a safety mechanism in case client code does not call Dispose correctly. However, avoid referencing managed objects in finalizers, because you cannot be certain of the object’s state.

If a disposable class extends another disposable class, the Dispose method of the child must call the Dispose method of its base class. Wrap the child’s code in a try block and call the parent’s Dispose method in a finally clause to ensure execution.

Other instance methods and properties of the class should throw a System. ObjectDisposedException exception if client code attempts to execute a method on an already disposed object.

The Code

The following example demonstrates a common implementation of the Dispose pattern.

using System;

namespace Apress.VisualCSharpRecipes.Chapter13

{

// Implement the IDisposable interface. public class DisposeExample : IDisposable

{

//Private data member to signal if the object has already been

//disposed.

bool isDisposed = false;

//Private data member that holds the handle to an unmanaged resource. private IntPtr resourceHandle;

//Constructor.

public DisposeExample()

{

// Constructor code obtains reference to unmanaged resource. resourceHandle = default(IntPtr);

}

//Destructor / Finalizer. Because Dispose calls GC.SuppressFinalize,

//this method is called by the garbage collection process only if

//the consumer of the object does not call Dispose as it should. ~DisposeExample()

{

//Call the Dispose method as opposed to duplicating the code to

//clean up any unmanaged resources. Use the protected Dispose

//overload and pass a value of "false" to indicate that Dispose is

//being called during the garbage collection process, not by

482 C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

// consumer code. Dispose(false);

}

//Public implementation of the IDisposable.Dispose method, called

//by the consumer of the object in order to free unmanaged resources

//deterministically.

public void Dispose()

{

//Call the protected Dispose overload and pass a value of "true"

//to indicate that Dispose is being called by consumer code, not

//by the garbage collector.

Dispose(true);

//Because the Dispose method performs all necessary cleanup,

//ensure the garbage collector does not call the class destructor. GC.SuppressFinalize(this);

}

//Protected overload of the Dispose method. The disposing argument

//signals whether the method is called by consumer code (true), or by

//the garbage collector (false). Note that this method is not part of

//the IDisposable interface because it has a different signature to the

//parameterless Dispose method.

protected virtual void Dispose(bool disposing)

{

// Don't try to Dispose of the object twice. if (!isDisposed)

{

//Determine if consumer code or the garbage collector is

//calling. Avoid referencing other managed objects during

//finalization.

if (disposing)

{

//Method called by consumer code. Call the Dispose method

//of any managed data members that implement the

//IDisposable interface.

//...

}

//Whether called by consumer code or the garbage collector,

//free all unmanaged resources and set the value of managed

//data members to null.

//Close(resourceHandle);

//In the case of an inherited type, call base.Dispose(disposing).

}

// Signal that this object has been disposed. isDisposed = true;

}

//Before executing any functionality, ensure that Dispose has not

//already been executed on the object.

public void SomeMethod()

{

// Throw an exception if the object has already been disposed.

C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

483

if (isDisposed)

{

throw new ObjectDisposedException("DisposeExample");

}

//Execute method functionality.

//. . .

}

}

// A class to demonstrate the use of DisposeExample. Public class Recipe13_06

{

public static void Main()

{

//The using statement ensures the Dispose method is called

//even if an exception occurs.

using (DisposeExample d = new DisposeExample())

{

// Do something with d.

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter"); Console.ReadLine();

}

}

}

13-7. Implement a Formattable Type

Problem

You need to implement a type that can create different string representations of its content based on the use of format specifiers, for use in formatted strings.

Solution

Implement the System.IFormattable interface.

How It Works

The following code fragment demonstrates the use of format specifiers in the WriteLine method of the System.Console class. The codes in the braces (emphasized in the example) are the format specifiers.

double a = 345678.5678; uint b = 12000;

byte c = 254;

Console.WriteLine("a = {0}, b = {1}, and c = {2}", a, b, c); Console.WriteLine("a = {0:c0}, b = {1:n4}, and c = {2,10:x5}", a, b, c);

484 C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

When run on a machine configured with English (U.K.) regional settings, this code will result in the output shown here.

a

=

345678.5678, b = 12000, and c = 254

 

a

=

£345,679, b = 12,000.0000, and c =

000fe

 

 

 

 

As you can see, changing the contents of the format specifiers changes the format of the output significantly, even though the data has not changed. To enable support for format specifiers in your own types, you must implement the IFormattable interface. IFormattable declares a single method named ToString with the following signature:

string ToString(string format, IFormatProvider formatProvider);

The format argument is a System.String containing a format string. The format string is the portion of the format specifier that follows the colon. For example, in the format specifier {2,10:x5} used in the previous example, x5 is the format string. The format string contains the instructions the IFormattable instance should use when it’s generating the string representation of its content. The

.NET Framework documentation for IFormattable states that types that implement IFormattable must support the G (general) format string, but that the other supported format strings depend on the implementation. The format argument will be null if the format specifier does not include a format string component; for example, {0} or {1,20}.

The formatProvider argument is a reference to an instance of a type that implements System. IFormatProvider, and which provides access to information about the cultural and regional preferences to use when generating the string representation of the IFormattable object. This information includes data such as the appropriate currency symbol or number of decimal places to use. By default, formatProvider is null, which means you should use the current thread’s regional and cultural settings, available through the static method CurrentCulture of the System.Globalization. CultureInfo class. Some methods that generate formatted strings, such as String.Format, allow you to specify an alternative IFormatProvider to use such as CultureInfo, DateTimeFormatInfo, or NumberFormatInfo.

The .NET Framework uses IFormattable primarily to support the formatting of value types, but it can be used to good effect with any type.

The Code

The following example contains a class named Person that implements the IFormattable interface. The Person class contains the title and names of a person and will render the person’s name in different formats depending on the format strings provided. The Person class does not make use of regional and cultural settings provided by the formatProvider argument. The Main method demonstrates how to use the formatting capabilities of the Person class.

using System;

namespace Apress.VisualCSharpRecipes.Chapter13

{

public class Person : IFormattable

{

//Private members to hold the person's title and name details. private string title;

private string[] names;

//Constructor used to set the person's title and names. public Person(string title, params string[] names)

{

C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

485

this.title = title; this.names = names;

}

//Override the Object.ToString method to return the person's

//name using the general format.

public override string ToString()

{

return ToString("G", null);

}

//Implementation of the IFormattable.ToString method to return the

//person's name in different forms based on the format string

//provided.

public string ToString(string format, IFormatProvider formatProvider)

{

string result = null;

//Use the general format if none is specified. if (format == null) format = "G";

//The contents of the format string determine the format of the

//name returned.

switch (format.ToUpper()[0])

{

case 'S':

// Use short form - first initial and surname.

result = names[0][0] + ". " + names[names.Length - 1]; break;

case 'P':

//Use polite form - title, initials, and surname

//Add the person's title to the result.

if (title != null && title.Length != 0)

{

result = title + ". ";

}

// Add the person's initials and surname.

for (int count = 0; count < names.Length; count++)

{

if (count != (names.Length - 1))

{

result += names[count][0] + ". ";

}

else

{

result += names[count];

}

}

break;

case 'I':

// Use informal form - first name only. result = names[0];

break;

486 C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

case 'G': default:

// Use general/default form - first name and surname. result = names[0] + " " + names[names.Length - 1]; break;

}

return result;

}

}

// A class to demonstrate the use of Person. Public class Recipe13_07

{

public static void Main()

{

//Create a Person object representing a man with the name

//Mr. Richard Glen David Peters.

Person person =

new Person("Mr", "Richard", "Glen", "David", "Peters");

//Display the person's name using a variety of format strings. System.Console.WriteLine("Dear {0:G},", person); System.Console.WriteLine("Dear {0:P},", person); System.Console.WriteLine("Dear {0:I},", person); System.Console.WriteLine("Dear {0},", person); System.Console.WriteLine("Dear {0:S},", person);

//Wait to continue.

Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter"); Console.ReadLine();

}

}

}

Usage

When executed, the preceding example produces the following output:

Dear Richard Peters,

Dear Mr. R. G. D. Peters,

Dear Richard,

Dear Richard Peters,

Dear R. Peters,

13-8. Implement a Custom Exception Class

Problem

You need to create a custom exception class so that you can use the runtime’s exception-handling mechanism to handle application-specific exceptions.

C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

487

Solution

Create a serializable class that extends the System.Exception class. Add support for any custom data members required by the exception, including constructors and properties required to manipulate the data members.

Tip If you need to define a number of custom exceptions for use in a single application or library, you should define a single custom exception that extends System.Exception and use this as a common base class for all of your other custom exceptions. There is very little point in extending System.Application, as is often recommended. Doing so simply introduces another level in your exception hierarchy and provides little if any benefit when handling your exception classes—after all, catching a nonspecific exception like ApplicationException is just as bad a practice as catching Exception.

How It Works

Exception classes are unique in the fact that you do not declare new classes solely to implement new or extended functionality. The runtime’s exception-handling mechanism—exposed by the C# statements try, catch, and finally—works based on the type of exception thrown, not the functional or data members implemented by the thrown exception.

If you need to throw an exception, you should use an existing exception class from the .NET Framework class library, if a suitable one exists. For example, some useful exceptions include the following:

System.ArgumentNullException, when code passes a null argument value to your method that does not support null arguments

System.ArgumentOutOfRangeException, when code passes an inappropriately large or small argument value to your method

System.FormatException, when code attempts to pass your method a String argument containing incorrectly formatted data

If none of the existing exception classes meets your needs, or you feel your application would benefit from using application-specific exceptions, it’s a simple matter to create your own exception class. In order to integrate your custom exception with the runtime’s exception-handling mechanism and remain consistent with the pattern implemented by .NET Framework–defined exception classes, you should do the following:

Give your exception class a meaningful name ending in the word Exception, such as

TypeMismatchException or RecordNotFoundException.

Mark your exception class as sealed if you do not intend other exception classes to extend it.

Implement additional data members and properties to support custom information that the exception class should provide.

Implement three public constructors with the signatures shown here and ensure they call the base class constructor.

public CustomException() : base() {}

public CustomException(string msg): base(msg) {}

public CustomException(string msg, Exception inner) : base(msg, inner) {}

Соседние файлы в предмете Программирование на C++