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

Visual CSharp 2005 Recipes (2006) [eng]

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

488C 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

Make your exception class serializable so that the runtime can marshal instances of your exception across application domain and machine boundaries. Applying the attribute System. SerializableAttribute is sufficient for exception classes that do not implement custom data members. However, because Exception implements the interface System.Runtime. Serialization.ISerializable, if your exception declares custom data members, you must override the ISerializable.GetObjectData method of the Exception class as well as implement a deserialization constructor with this signature. If your exception class is sealed, mark the deserialization constructor as private; otherwise, mark it as protected. The GetObjectData method and deserialization constructor must call the equivalent base class method to allow the base class to serialize and deserialize its data correctly. (See recipe 13-1 for details on making classes serializable.)

Tip In large applications, you will usually implement quite a few custom exception classes. It pays to put significant thought into how you organize your custom exceptions and how code will use them. Generally, avoid creating new exception classes unless code will make specific efforts to catch that exception; use data members to achieve informational granularity, not additional exception classes. In addition, avoid deep class hierarchies when possible in favor of broad, shallow hierarchies.

The Code

The following example is a custom exception named CustomException that extends Exception and declares two custom data members: a string named stringInfo and a bool named booleanInfo.

using System;

using System.Runtime.Serialization;

namespace Apress.VisualCSharpRecipes.Chapter13

{

// Mark CustomException as Serializable. [Serializable]

public sealed class CustomException : Exception

{

//Custom data members for CustomException. private string stringInfo;

private bool booleanInfo;

//Three standard constructors and simply call the base class

//constructor (System.Exception).

public CustomException() : base() { }

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

public CustomException(string message, Exception inner)

:base(message, inner) { }

//The deserialization constructor required by the ISerialization

//interface. Because CustomException is sealed, this constructor

//is private. If CustomException were not sealed, this constructor

//should be declared as protected so that derived classes can call

//it during deserialization.

private CustomException(SerializationInfo info, StreamingContext context) : base(info, context)

{

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

489

// Deserialize each custom data member. stringInfo = info.GetString("StringInfo"); booleanInfo = info.GetBoolean("BooleanInfo");

}

//Additional constructors to allow code to set the custom data

//members.

public CustomException(string message, string stringInfo, bool booleanInfo) : this(message)

{

this.stringInfo = stringInfo; this.booleanInfo = booleanInfo;

}

public CustomException(string message, Exception inner, string stringInfo, bool booleanInfo): this(message, inner)

{

this.stringInfo = stringInfo; this.booleanInfo = booleanInfo;

}

// Read-only properties that provide access to the custom data members. public string StringInfo

{

get { return stringInfo; }

}

public bool BooleanInfo

{

get { return booleanInfo; }

}

//The GetObjectData method (declared in the ISerializable interface)

//is used during serialization of CustomException. Because

//CustomException declares custom data members, it must override the

//base class implementation of GetObjectData.

public override void GetObjectData(SerializationInfo info, StreamingContext context)

{

//Serialize the custom data members. info.AddValue("StringInfo", stringInfo); info.AddValue("BooleanInfo", booleanInfo);

//Call the base class to serialize its members. base.GetObjectData(info, context);

}

//Override the base class Message property to include the custom data

//members.

public override string Message

{

get

{

string message = base.Message; if (stringInfo != null)

{

message += Environment.NewLine +

490 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

stringInfo + " = " + booleanInfo;

}

return message;

}

}

}

// A class to demonstrate the use of CustomException. Public class Recipe13_08

{

public static void Main()

{

try

{

// Create and throw a CustomException object. throw new CustomException("Some error",

"SomeCustomMessage", true);

}

catch (CustomException ex)

{

Console.WriteLine(ex.Message);

}

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

}

}

}

13-9. Implement a Custom Event Argument

Problem

When you raise an event, you need to pass an event-specific state to the event handlers.

Solution

Create a custom event argument class derived from the System.EventArg class. When you raise the event, create an instance of your event argument class and pass it to the event handlers.

How It Works

When you declare your own event types, you will often want to pass event-specific state to any listening event handlers. To create a custom event argument class that complies with the Event pattern defined by the .NET Framework, you should do the following:

Derive your custom event argument class from the EventArgs class. The EventArgs class contains no data and is used with events that do not need to pass event state.

Give your event argument class a meaningful name ending in EventArgs, such as

DiskFullEventArgs or MailReceivedEventArgs.

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

491

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

Implement additional data members and properties to support event state that you need to pass to event handlers. It’s best to make event state immutable, so you should use private readonly data members and use public properties to provide read-only access to the data members.

Implement a public constructor that allows you to set the initial configuration of the event state.

Make your event argument class serializable so that the runtime can marshal instances of it across application domain and machine boundaries. Applying the attribute System. SerializableAttribute is usually sufficient for event argument classes. However, if your class has special serialization requirements, you must also implement the interface System.Runtime. Serialization.ISerializable. (See recipe 13-1 for details on making classes serializable.)

The Code

The following example demonstrates the implementation of an event argument class named MailReceivedEventArgs. Theoretically, an e-mail server passes instances of the MailReceivedEventArgs class to event handlers in response to the receipt of an e-mail message. The MailReceivedEventArgs class contains information about the sender and subject of the received e-mail message.

using System;

namespace Apress.VisualCSharpRecipes.Chapter13

{

[Serializable]

public sealed class MailReceivedEventArgs : EventArgs

{

//Private read-only members that hold the event state that is to be

//distributed to all event handlers. The MailReceivedEventArgs class

//will specify who sent the received mail and what the subject is. private readonly string from;

private readonly string subject;

//Constructor, initializes event state.

public MailReceivedEventArgs(string from, string subject)

{

this.from = from; this.subject = subject;

}

// Read-only properties to provide access to event state. public string From { get { return from; } }

public string Subject { get { return subject; } }

}

// A class to demonstrate the use of MailReceivedEventArgs. Public class Recipe13_09

{

public static void Main()

{

MailReceivedEventArgs args =

new MailReceivedEventArgs("Danielle", "Your book");

492 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

Console.WriteLine("From: {0}, Subject: {1}", args.From, args.Subject);

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

}

}

}

13-10. Implement the Singleton Pattern

Problem

You need to ensure that only a single instance of a type exists at any given time and that the single instance is accessible to all elements of your application.

Solution

Implement the type using the Singleton pattern.

How It Works

Of all the identified patterns, the Singleton pattern is perhaps the most widely known and commonly used. The purpose of the Singleton pattern is to ensure that only one instance of a type exists at a given time and to provide global access to the functionality of that single instance. You can implement the type using the Singleton pattern by doing the following:

Implement a private static member within the type to hold a reference to the single instance of the type.

Implement a publicly accessible static property in the type to provide read-only access to the singleton instance.

Implement only a private constructor so that code cannot create additional instances of the type.

The Code

The following example demonstrates an implementation of the Singleton pattern for a class named

SingletonExample.

using System;

namespace Apress.VisualCSharpRecipes.Chapter13

{

public class SingletonExample

{

//A static member to hold a reference to the singleton instance. private static SingletonExample instance;

//A static constructor to create the singleton instance. Another

//alternative is to use lazy initialization in the Instance property. static SingletonExample()

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

493

{

instance = new SingletonExample();

}

//A private constructor to stop code from creating additional

//instances of the singleton type.

private SingletonExample() { }

//A public property to provide access to the singleton instance. public static SingletonExample Instance

{

get { return instance; }

}

//Public methods that provide singleton functionality.

public void SomeMethod1() { /*..*/ } public void SomeMethod2() { /*..*/ }

}

}

Usage

To invoke the functionality of the SingletonExample class, you can obtain a reference to the singleton using the Instance property and then call its methods. Alternatively, you can execute members of the singleton directly through the Instance property. The following code shows both approaches.

//Obtain reference to singleton and invoke methods SingletonExample s = SingletonExample.Instance; s.SomeMethod1();

//Execute singleton functionality without a reference SingletonExample.Instance.SomeMethod2();

13-11. Implement the Observer Pattern

Problem

You need to implement an efficient mechanism for an object (the subject) to notify other objects (the observers) about changes to its state.

Solution

Implement the Observer pattern using delegate types as type-safe function pointers and event types to manage and notify the set of observers.

How It Works

The traditional approach to implementing the Observer pattern is to implement two interfaces: one to represent an observer (IObserver) and the other to represent the subject (ISubject). Objects that implement IObserver register with the subject, indicating that they want to be notified of important events (such as state changes) affecting the subject. The subject is responsible for managing the list of registered observers and notifying them in response to events affecting the subject. The subject

494C 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

usually notifies observers by calling a Notify method declared in the IObserver interface. The subject might pass data to the observer as part of the Notify method, or the observer might need to call a method declared in the ISubject interface to obtain additional details about the event.

Although you are free to implement the Observer pattern in C# using the approach just described, the Observer pattern is so pervasive in modern software solutions that C# and the .NET Framework include event and delegate types to simplify its implementation. The use of events and delegates means that you do not need to declare IObserver and ISubject interfaces. In addition, you do not need to implement the logic necessary to manage and notify the set of registered observers— the area where most coding errors occur.

The .NET Framework uses one particular implementation of the event-based and delegate-based Observer pattern so frequently that it has been given its own name: the Event pattern. (Pattern purists might prefer the name Event idiom, but Event pattern is the name most commonly used in Microsoft documentation.)

The Code

The example for this recipe contains a complete implementation of the Event pattern, which includes the following types:

Thermostat class (the subject of the example), which keeps track of the current temperature and notifies observers when a temperature change occurs

TemperatureChangeEventArgs class, which is a custom implementation of the System.EventArgs class used to encapsulate temperature change data for distribution during the notification of observers

TemperatureEventHandler delegate, which defines the signature of the method that all observers of a Thermostat object must implement, and which a Thermostat object will call in the event of temperature changes

TemperatureChangeObserver and TemperatureAverageObserver classes, which are observers of the Thermostat class

The TemperatureChangeEventArgs class (in the following listing) derives from the class System. EventArgs. The custom event argument class should contain all of the data that the subject needs to pass to its observers when it notifies them of an event. If you do not need to pass data with your event notifications, you do not need to define a new argument class; simply pass EventArgs.Empty or null as the argument when you raise the event. (See recipe 13-9 for details on implementing custom event argument classes.)

namespace Apress.VisualCSharpRecipes.Chapter13

{

//An event argument class that contains information about a temperature

//change event. An instance of this class is passed with every event. public class TemperatureChangedEventArgs : EventArgs

{

//Private data members contain the old and new temperature readings. private readonly int oldTemperature, newTemperature;

//Constructor that takes the old and new temperature values.

public TemperatureChangedEventArgs(int oldTemp, int newTemp)

{

oldTemperature = oldTemp; newTemperature = newTemp;

}

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

495

// Read-only properties provide access to the temperature values. public int OldTemperature { get { return oldTemperature; } } public int NewTemperature { get { return newTemperature; } }

}

}

The following code shows the declaration of the TemperatureEventHandler delegate. Based on this declaration, all observers must implement a method (the name is unimportant), which returns void and takes two arguments: an Object instance as the first argument and

a TemperatureChangeEventArgs object as the second. During notification, the Object argument is a reference to the Thermostat object that raises the event, and the TemperatureChangeEventArgs argument contains data about the old and new temperature values.

namespace Apress.VisualCSharpRecipes.Chapter13

{

//A delegate that specifies the signature that all temperature event

//handler methods must implement.

public delegate void TemperatureChangedEventHandler(Object sender, TemperatureChangeEventArgs args);

}

For the purpose of demonstrating the Observer pattern, the example contains two different observer types: TemperatureAverageObserver and TemperatureChangeObserver. Both classes have the same basic implementation. TemperatureAverageObserver keeps a count of the number of temperature change events and the sum of the temperature values, and displays an average temperature when each event occurs. TemperatureChangeObserver displays information about the change in temperature each time a temperature change event occurs.

The following listing shows the TemperatureChangeObserver and TemperatureAverageObserver classes. Notice that the constructors take references to the Thermostat object that the

TemperatureChangeObserver or TemperatureAverageObserver object should observe. When you instantiate an observer, pass it a reference to the subject. The observer must create a delegate instance containing a reference to the observer’s event-handler method. To register as an observer, the observer object must then add its delegate instance to the subject using the subject’s public event member. This is made even easier with the simplified delegate syntax provided by C# 2.0, where it is no longer required to explicitly instantiate a delegate to wrap the listening method.

Once the TemperatureChangeObserver or TemperatureAverageObserver object has registered its delegate instance with the Thermostat object, you need to maintain a reference to this Thermostat object only if you want to stop observing it later on. In addition, you do not need to maintain a reference to the subject, because a reference to the event source is included as the first argument each time the Thermostat object raises an event through the TemperatureChange method.

namespace Apress.VisualCSharpRecipes.Chapter13

{

//A Thermostat observer that displays information about the change in

//temperature when a temperature change event occurs.

public class TemperatureChangeObserver

{

//A constructor that takes a reference to the Thermostat object that

//the TemperatureChangeObserver object should observe.

public TemperatureChangeObserver(Thermostat t)

{

//Create a new TemperatureChangedEventHandler delegate instance and

//register it with the specified Thermostat.

t.TemperatureChanged += this.TemperatureChange;

}

496 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

// The method to handle temperature change events. public void TemperatureChange(Object sender,

TemperatureChangedEventArgs temp)

{

Console.WriteLine ("ChangeObserver: Old={0}, New={1}, Change={2}", temp.OldTemperature, temp.NewTemperature,

temp.NewTemperature - temp.OldTemperature);

}

}

//A Thermostat observer that displays information about the average

//temperature when a temperature change event occurs.

public class TemperatureAverageObserver

{

//Sum contains the running total of temperature readings.

//Count contains the number of temperature events received. private int sum = 0, count = 0;

//A constructor that takes a reference to the Thermostat object that

//the TemperatureAverageObserver object should observe.

public TemperatureAverageObserver(Thermostat t)

{

//Create a new TemperatureChangedEventHandler delegate instance and

//register it with the specified Thermostat.

t.TemperatureChanged += this.TemperatureChange;

}

// The method to handle temperature change events. public void TemperatureChange(Object sender,

TemperatureChangedEventArgs temp)

{

count++;

sum += temp.NewTemperature;

Console.WriteLine

("AverageObserver: Average={0:F}", (double)sum / (double)count);

}

}

}

Finally, the Thermostat class is the observed object in this Observer (Event) pattern. In theory, a monitoring device sets the current temperature by calling the Temperature property on

a Thermostat object. This causes the Thermostat object to raise its TemperatureChange event and send a TemperatureChangeEventArgs object to each observer.

The example contains a Recipe13_11 class that defines a Main method to drive the example. After creating a Thermostat object and two different observer objects, the Main method repeatedly prompts you to enter a temperature. Each time you enter a new temperature, the Thermostat object notifies the listeners, which display information to the console. The following is the code for the

Thermostat class.

namespace Apress.VisualCSharpRecipes.Chapter13

{

//A class that represents a Thermostat, which is the source of temperature

//change events. In the Observer pattern, a Thermostat object is the

//Subject that Observers listen to for change notifications.

public class Thermostat

{

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

497

//Private field to hold current temperature. private int temperature = 0;

//The event used to maintain a list of observer delegates and raise

//a temperature change event when a temperature change occurs. public event TemperatureChangedEventHandler TemperatureChanged;

//A protected method used to raise the TemperatureChanged event.

//Because events can be triggered only from within the containing

//type, using a protected method to raise the event allows derived

//classes to provide customized behavior and still be able to raise

//the base class event.

virtual protected void OnTemperatureChanged (TemperatureChangedEventArgs args)

{

//Notify all observers. A test for null indicates whether any

//observers are registered.

if (TemperatureChanged != null)

{

TemperatureChanged(this, args);

}

}

//Public property to get and set the current temperature. The "set"

//side of the property is responsible for raising the temperature

//change event to notify all observers of a change in temperature. public int Temperature

{

get { return temperature; }

set

{

//Create a new event argument object containing the old and

//new temperatures.

TemperatureChangedEventArgs args =

new TemperatureChangedEventArgs(temperature, value);

//Update the current temperature. temperature = value;

//Raise the temperature change event. OnTemperatureChanged(args);

}

}

}

// A class to demonstrate the use of the Observer pattern. public class Recipe13_11

{

public static void Main()

{

//Create a Thermostat instance. Thermostat t = new Thermostat();

//Create the Thermostat observers. new TemperatureChangeObserver(t); new TemperatureAverageObserver(t);

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