Tải bản đầy đủ (.pdf) (88 trang)

C# 3.0 Cookbook phần 5 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (283.94 KB, 88 trang )

Converting Delegate Invocation from Synchronous to Asynchronous
|
327
In the TestIndividualInvokesExceptions method of this recipe, if an exception is
caught, it is logged to the event log and displayed, then the code continues to invoke
delegates. This strategy allows for as fine-grained handling of exceptions as you
need. One way to deal with this is to store all of the exceptions that occur during del-
egate processing, and then wrap all of the exceptions encountered during processing
in a custom exception. After processing completes, throw the custom exception. See
the
MulticastInvocationException class in the Solution.
By adding a
finally block to this try-catch block, you could be assured that code
within this
finally block is executed after every delegate returns. This technique is
useful if you want to interleave code between calls to delegates, such as code to clean
up objects that are not needed or code to verify that each delegate left the data it
touched in a stable state.
See Also
Recipes 9.1 and 9.2; see the “Delegate Class” and “Delegate.GetInvocationList
Method” topics in the MSDN documentation.
9.4 Converting Delegate Invocation from
Synchronous to Asynchronous
Problem
You have determined that one or more delegates invoked synchronously within your
application are taking a long time to execute. This delay is making the user interface
less responsive to the user. The invocation of these delegates should be converted
from synchronous to asynchronous mode.
Solution
A typical synchronous delegate type and supporting code that invokes the delegate
are shown here:


public delegate void SyncDelegateTypeSimple( );
public class TestSyncDelegateTypeSimple
{
public static void Method1( )
{
Console.WriteLine("Invoked Method1");
}
}
The code to use this delegate is:
public static void TestSimpleSyncDelegate( )
{
328
|
Chapter 9: Delegates, Events, and Lambda Expressions
SyncDelegateTypeSimple sdtsInstance = TestSyncDelegateTypeSimple.Method1;
sdtsInstance( );
}
This delegate can be called asynchronously on a thread obtained from the thread
pool by modifying the code as follows:
public static void TestSimpleAsyncDelegate( )
{
AsyncCallback callBack = new AsyncCallback(DelegateSimpleCallback);
SyncDelegateTypeSimple sdtsInstance = TestSyncDelegateTypeSimple.Method1;
IAsyncResult asyncResult =
sdtsInstance.BeginInvoke(callBack, null);
Console.WriteLine("WORKING ");
}
// The callback that gets called when TestSyncDelegateTypeSimple.Method1
// is finished processing
private static void DelegateSimpleCallback(IAsyncResult iResult)

{
AsyncResult result = (AsyncResult)iResult;
SyncDelegateTypeSimple sdtsInstance =
(SyncDelegateTypeSimple)result.AsyncDelegate;
sdtsInstance.EndInvoke(result);
Console.WriteLine("Simple callback run");
}
AsyncResult can be found in the System.Runtime.Remoting.Messaging
namespace in mscorlib.
Of course, you might also want to change the TestSyncDelegateTypeSimple class
name to
TestAsyncDelegateTypeSimple and the SyncDelegateTypeSimple delegate
name to
AsyncDelegateTypeSimple just to be consistent with your naming.
The previous example shows how to call a delegate that accepts no parameters and
returns
void. The next example shows a synchronous delegate that accepts parame-
ters and returns an integer:
public delegate int SyncDelegateType(string message);
public class TestSyncDelegateType
{
public static int Method1(string message)
{
Console.WriteLine("Invoked Method1 with message: " + message);
return 1;
}
}
Converting Delegate Invocation from Synchronous to Asynchronous
|
329

The code to use this delegate is:
public static void TestComplexSyncDelegate( )
{
SyncDelegateType sdtInstance = TestSyncDelegateType.Method1;
int retVal = sdtInstance("Synchronous call");
Console.WriteLine("Sync: " + retVal);
}
The synchronous invocation of the delegate can be converted to asynchronous invo-
cation in the following manner:
public static void TestCallbackAsyncDelegate( )
{
AsyncCallback callBack =
new AsyncCallback(DelegateCallback);
SyncDelegateType sdtInstance = TestSyncDelegateType.Method1;
IAsyncResult asyncResult =
sdtInstance.BeginInvoke("Asynchronous call", callBack, null);
Console.WriteLine("WORKING ");
}
// The callback that gets called when TestSyncDelegateType.Method1
// is finished processing
private static void DelegateCallback(IAsyncResult iResult)
{
AsyncResult result = (AsyncResult)iResult;
SyncDelegateType sdtInstance =
(SyncDelegateType)result.AsyncDelegate;
int retVal = sdtInstance.EndInvoke(result);
Console.WriteLine("retVal (Callback): " + retVal);
}
Discussion
Converting the invocation of a delegate from being synchronous to asynchronous is

not an overly complicated procedure. You need to add calls to both
BeginInvoke and
EndInvoke on the delegate that is being called synchronously. A callback method,
DelegateCallback, is added, which gets called when the delegate is finished. This call-
back method then calls the
EndInvoke method on the delegate invoked using
BeginInvoke.
You must always call EndInvoke when invoking delegates asynchro-
nously, even when the delegate returns void, to ensure proper cleanup
of resources in the CLR.
330
|
Chapter 9: Delegates, Events, and Lambda Expressions
The notification callback method specified in the callback parameter accepts a sin-
gle parameter of type
IAsyncResult. This parameter can be cast to an AsyncResult
type and used to set up the call to the EndInvoke method. If you want to handle any
exceptions thrown by the asynchronous delegate in the notification callback, wrap
the
EndInvoke method in a try/catch exception handler.
See Also
The “Delegate Class” and “Asynchronous Delegates” topics in the MSDN
documentation.
9.5 An Advanced Interface Search Mechanism
Problem
You are searching for an interface using the Type class. However, complex interface
searches are not available through the
GetInterface and GetInterfaces methods of a
Type object. The GetInterface method searches for an interface only by name (using
a case-sensitive or case-insensitive search), and the

GetInterfaces method returns an
array of all the interfaces implemented on a particular type. You want a more
focused searching mechanism that might involve searching for interfaces that define
a method with a specific signature or implemented interfaces that are loaded from
the GAC. You need more flexible and more advanced searching for interfaces that
does not involve creating your own interface search engine. This capability might be
used for applications like a code generator or reverse engineering tool.
Solution
Use LINQ to query the type interface information and perform rich searches. The
method shown in Example 9-3 will demonstrate one complex search that can be per-
formed with LINQ.
Example 9-3. Performing complex searches of interfaces on a type
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
public class SearchType
{
public static void FindSpecificInterfaces( )
{
// set up the interfaces to search for
Type[] interfaces = {
typeof(System.ICloneable),
typeof(System.Collections.ICollection),
An Advanced Interface Search Mechanism
|
331
The FindSpecificInterfaces method searches for any of the three interface types
contained in the

Names array that are implemented by the System.Collections.
ArrayList
type. It does this by using LINQ to query if the type is an instance of any
of the set of interfaces.
Discussion
There are many ways to use LINQ to search for interfaces implemented on a type—
here are just a few other searches that can be performed:
• A search for all implemented interfaces that are defined within a particular
namespace (in this case, the
System.Collections namespace):
var collectionsInterfaces = from type in searchType.GetInterfaces( )
where type.Namespace == "System.Collections"
select type;
• A search for all implemented interfaces that contain a method called Add, which
returns an
Int32 value:
var addInterfaces = from type in searchType.GetInterfaces( )
from method in type.GetMethods( )
where (method.Name == "Add") &&
(method.ReturnType == typeof(int))
select type;
• A search for all implemented interfaces that are loaded from the GAC:
var gacInterfaces = from type in searchType.GetInterfaces( )
where type.Assembly.GlobalAssemblyCache
select type;
• A search for all implemented interfaces that are defined within an assembly with
the version number 2.0.0.0:
var versionInterfaces = from type in searchType.GetInterfaces( )
where type.Assembly.GlobalAssemblyCache
select type;

typeof(System.IAppDomainSetup) };
// set up the type to examine
Type searchType = typeof(System.Collections.ArrayList);
var matches = from t in searchType.GetInterfaces( )
join s in interfaces on t equals s
select s;
Console.WriteLine("Matches found:");
foreach (Type match in matches)
{
Console.WriteLine(match.ToString( ));
}
}
}
Example 9-3. Performing complex searches of interfaces on a type (continued)
332
|
Chapter 9: Delegates, Events, and Lambda Expressions
See Also
The “Lambda Expressions (C# Programming Guide)” and “where keyword [LINQ]
(C#)” topics in the MSDN documentation.
9.6 Observing Additions and Modifications to
Dictionaries
Problem
You have multiple objects that need to observe modifications to objects that imple-
ment
IDictionary<K,V>. When an item is added or modified in the dictionary-based
collection, each of these observer objects should be able to vote to allow or disallow
the action. In order for an action to be allowed to complete, all observer objects must
state if they are vetoing the action. If even one observer object votes to disallow the
action, the action is prevented.

Solution
Use the ObservableDictionaryObserver class implemented in Example 9-5 to observe
additions and modifications to the
ObservableDictionary class (shown in Example 9-4)
object that is registered with this object. The
ObservableDictionary class is a generic
wrapper for collections that implement
IDictionary<K,V> and allows itself to be
observed by the
ObservableDictionaryObserver class.
The
ObservableDictionaryEventArgs class is a specialization of the EventArgs class,
which provides the
IDictionary<K,V> key and value being added or modified to the
ObservableDictionaryObserver object, as well as a Boolean property, KeepChanges.
This flag indicates whether the addition or modification in the
ObservableDictionary
object will succeed or be rolled back. The MakeObservableDictionary extension
method for
IDictionary<K,V> wraps up the code for creating an
ObservableDictionary from an IDictionary instance. Example 9-4 illustrates the two
classes and the extension method.
Example 9-4. ObservableDictionary and ObservableDictionaryEventArgs classes and the
MakeObservableDictionary extension method
public class ObservableDictionary<TKey,TValue> : IDictionary<TKey,TValue>
{
IDictionary<TKey, TValue> _internalDictionary;
public ObservableDictionary(IDictionary<TKey,TValue> dictionary)
{
if (dictionary == null)

throw new ArgumentNullException("dictionary");
_internalDictionary = dictionary;
}
Observing Additions and Modifications to Dictionaries
|
333
#region Events and Event Initiation
public event EventHandler<ObservableDictionaryEventArgs<TKey,TValue>> AddingEntry;
public event EventHandler<ObservableDictionaryEventArgs<TKey, TValue>> AddedEntry;
public event EventHandler<ObservableDictionaryEventArgs<TKey, TValue>> ChangingEntry;
public event EventHandler<ObservableDictionaryEventArgs<TKey, TValue>> ChangedEntry;
protected virtual bool OnAdding(ObservableDictionaryEventArgs<TKey,TValue> e)
{
if (AddingEntry != null)
{
AddingEntry(this, e);
return (e.KeepChanges);
}
return (true);
}
protected virtual void OnAdded(ObservableDictionaryEventArgs<TKey, TValue> e)
{
if (AddedEntry != null)
{
AddedEntry(this, e);
}
}
protected virtual bool OnChanging(ObservableDictionaryEventArgs<TKey, TValue> e)
{
if (ChangingEntry != null)

{
ChangingEntry(this, e);
return (e.KeepChanges);
}
return (true);
}
protected virtual void OnChanged(ObservableDictionaryEventArgs<TKey, TValue> e)
{
if (ChangedEntry != null)
{
ChangedEntry(this, e);
}
}
#endregion // Events and Event Initiation
#region Interface implementations
#region IDictionary<TKey,TValue> Members
public ICollection<TValue> Values
{
Example 9-4. ObservableDictionary and ObservableDictionaryEventArgs classes and the
MakeObservableDictionary extension method (continued)
334
|
Chapter 9: Delegates, Events, and Lambda Expressions
get { return _internalDictionary.Values; }
}
public ICollection<TKey> Keys
{
get { return _internalDictionary.Keys; }
}
public TValue this[TKey key]

{
get
{
TValue value;
if (_internalDictionary.TryGetValue(key, out value))
return value;
else
{
return default(TValue);
}
}
set
{
// see if this key is there to be changed, if not add it
if (_internalDictionary.ContainsKey(key))
{
ObservableDictionaryEventArgs<TKey, TValue> args =
new ObservableDictionaryEventArgs<TKey, TValue>(key, value);
if (OnChanging(args))
{
_internalDictionary[key] = value;
}
else
{
Debug.WriteLine("Change of value cannot be performed");
}
OnChanged(args);
}
else
{

Debug.WriteLine("Item did not exist, adding");
_internalDictionary.Add(key, value);
}
}
}
public void Add(TKey key, TValue value)
{
ObservableDictionaryEventArgs<TKey, TValue> args =
new ObservableDictionaryEventArgs<TKey, TValue>(key, value);
Example 9-4. ObservableDictionary and ObservableDictionaryEventArgs classes and the
MakeObservableDictionary extension method (continued)
Observing Additions and Modifications to Dictionaries
|
335
if (OnAdding(args))
{
this._internalDictionary.Add(key, value);
}
else
{
Debug.WriteLine("Addition of key/value cannot be performed");
}
OnAdded(args);
}
public bool ContainsKey(TKey key)
{
return _internalDictionary.ContainsKey(key);
}
public bool Remove(TKey key)
{

return _internalDictionary.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value)
{
return _internalDictionary.TryGetValue(key, out value);
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
public void Add(KeyValuePair<TKey, TValue> item)
{
_internalDictionary.Add(item.Key, item.Value);
}
public void Clear( )
{
_internalDictionary.Clear( );
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _internalDictionary.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_internalDictionary.CopyTo(array, arrayIndex);
}
Example 9-4. ObservableDictionary and ObservableDictionaryEventArgs classes and the
MakeObservableDictionary extension method (continued)
336
|
Chapter 9: Delegates, Events, and Lambda Expressions
public int Count

{
get { return _internalDictionary.Count; }
}
public bool IsReadOnly
{
get { return _internalDictionary.IsReadOnly; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return _internalDictionary.Remove(item);
}
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator( )
{
return _internalDictionary.GetEnumerator( );
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator( )
{
return _internalDictionary.GetEnumerator( );
}
#endregion
#endregion // Interface implementations
}
public static ObservableDictionary<TKey, TValue> MakeObservableDictionary<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary)
{
return new ObservableDictionary<TKey, TValue>(dictionary);

}
public class ObservableDictionaryEventArgs<TKey, TValue> : EventArgs
{
TKey _key;
TValue _value;
public ObservableDictionaryEventArgs(TKey key, TValue value)
{
_key = key;
Example 9-4. ObservableDictionary and ObservableDictionaryEventArgs classes and the
MakeObservableDictionary extension method (continued)
Observing Additions and Modifications to Dictionaries
|
337
Example 9-5 shows the code for the ObservableDictionaryObserver class.
_value = value;
this.KeepChanges = true;
}
public bool KeepChanges { get; set; }
public TKey Key { get { return _key; } }
public TValue Value { get { return _value; } }
}
Example 9-5. ObservableDictionaryObserver class
// The observer object that will observe a registered ObservableDictionary object
public class ObservableDictionaryObserver<TKey,TValue>
{
public ObservableDictionaryObserver( ) { }
// set up delegate/events for approving an addition or change
public delegate bool Approval(object sender,
ObservableDictionaryEventArgs<TKey,TValue> e);
public Approval ApproveAdd { get; set; }

public Approval ApproveChange { get; set; }
public void Register(ObservableDictionary<TKey, TValue> dictionary)
{
// hook up to the ObservableDictionary instance events
dictionary.AddingEntry +=
new EventHandler<ObservableDictionaryEventArgs<TKey, TValue>>(
OnAddingListener);
dictionary.AddedEntry +=
new EventHandler<ObservableDictionaryEventArgs<TKey, TValue>>(
OnAddedListener);
dictionary.ChangingEntry +=
new EventHandler<ObservableDictionaryEventArgs<TKey, TValue>>(
OnChangingListener);
dictionary.ChangedEntry +=
new EventHandler<ObservableDictionaryEventArgs<TKey, TValue>>(
OnChangedListener);
}
public void Unregister(ObservableDictionary<TKey,TValue> dictionary)
{
// Unhook from the ObservableDictionary instance events
dictionary.AddingEntry -=
new EventHandler<ObservableDictionaryEventArgs<TKey, TValue>>(
OnAddingListener);
dictionary.AddedEntry -=
new EventHandler<ObservableDictionaryEventArgs<TKey, TValue>>(
OnAddedListener);
Example 9-4. ObservableDictionary and ObservableDictionaryEventArgs classes and the
MakeObservableDictionary extension method (continued)
338
|

Chapter 9: Delegates, Events, and Lambda Expressions
dictionary.ChangingEntry -=
new EventHandler<ObservableDictionaryEventArgs<TKey, TValue>>(
OnChangingListener);
dictionary.ChangedEntry -=
new EventHandler<ObservableDictionaryEventArgs<TKey, TValue>>(
OnChangedListener);
}
private void CheckApproval(Approval approval,
ObservableDictionaryEventArgs<TKey,TValue> args)
{
// check everyone who wants to approve
foreach (Approval approvalInstance in
approval.GetInvocationList( ))
{
if (!approvalInstance(this,args))
{
// if any of the concerned parties
// refuse, then no add. Adds by default
args.KeepChanges = false;
break;
}
}
}
private void OnAddingListener(object sender,
ObservableDictionaryEventArgs<TKey,TValue> args)
{
// see if anyone hooked up for approval
if (ApproveAdd != null)
{

CheckApproval(ApproveAdd, args);
}
Debug.WriteLine("[NOTIFY] Before Add : Add Approval = " +
args.KeepChanges.ToString( ));
}
private void OnAddedListener(object sender,
ObservableDictionaryEventArgs<TKey, TValue> args)
{
Debug.WriteLine("[NOTIFY] After Add: Item approved for adding: " +
args.KeepChanges.ToString( ));
}
private void OnChangingListener(object sender,
ObservableDictionaryEventArgs<TKey, TValue> args)
{
// see if anyone hooked up for approval
if (ApproveChange != null)
{
CheckApproval(ApproveChange, args);
Example 9-5. ObservableDictionaryObserver class (continued)
Observing Additions and Modifications to Dictionaries
|
339
Discussion
The observer design pattern allows one or more observer objects to act as spectators
over one or more subjects. Not only do the observer objects act as spectators, but
they can also induce change in the subjects. According to this pattern, any subject is
allowed to register itself with one or more observer objects. Once this is done, the
subject can operate as it normally does. The key feature is that the subject doesn’t
have to know what it is being observed by; this allows the coupling between subjects
and observers to be minimized. The observer object(s) will then be notified of any

changes in state to the subjects. When the subject’s state changes, the observer
object(s) can change the state of other objects in the system to bring them into line
with changes that were made to the subject(s). In addition, the observer could even
make changes or refuse changes to the subject(s) themselves.
The observer pattern is best implemented with events in C#. The event object pro-
vides a built-in way of implementing the observer design pattern. This recipe imple-
ments this pattern on all collections supporting
IDictionary<K,V>. The object being
observed must raise events for any listening observer objects to handle, but the
IDictionary<K,V> interface found in the FCL does not indicate any events. In order to
make the
IDictionary<K,V> raise events at specific times, you must implement a
wrapper class,
ObservableDictionary, that implements the IDictionary<K,V> inter-
face. This
ObservableDictionary class overrides the Add and indexer members of the
base interface. In addition, four events (
AddingEntry, AddedEntry, ChangingEntry, and
ChangedEntry) are created; they will be raised before and after items are added or
modified in the
ObservableDictionary. To raise these events, the following four
methods are created, one to raise each event:
• The
OnAdding method raises the AddingEntry event.
• The
OnAdded method raises the AddedEntry event.
• The
OnChanging method raises the ChangingEntry event.
• The
OnChanged method raises the ChangedEntry event.

}
Debug.WriteLine("[NOTIFY] Before Change : Change Approval = " +
args.KeepChanges.ToString( ));
}
private void OnChangedListener(object sender,
ObservableDictionaryEventArgs<TKey, TValue> args)
{
Debug.WriteLine("[NOTIFY] After Change: Item approved for change: " +
args.KeepChanges.ToString( ));
}
}
Example 9-5. ObservableDictionaryObserver class (continued)
340
|
Chapter 9: Delegates, Events, and Lambda Expressions
The Add method calls the OnAdding method, which then raises the event to any listen-
ing observer objects. The
OnAdding method is called before the Add method on the
internal dictionary is called. After the key/value pair has been added, the
OnAdded
method is called. This operation is similar to the indexer set method.
The Onxxx methods that raise the events in the ObservableDictionary
class are marked as protected virtual to allow classes to subclass this
class and implement their own method of dealing with the events.
Note that this statement is not applicable to
sealed classes. In those
cases, you can simply make the methods
public.
The ObservableDictionaryEventArgs class contains three private fields, defined as
follows:

key
The key that is to be added to the dictionary.
value
The value that is to be added to the dictionary.
keepChanges
A flag indicating whether the key/value pair should be added to the dictionary.
true indicates that this pair should be added to the dictionary.
The
keepChanges field is used by the observer to determine whether an add or change
operation should proceed. This flag is discussed further when you look at the
ObservableDictionaryObserver observer object.
The
ObservableDictionaryObserver is the observer object that watches any
ObservableDictionary objects it is told about. Any ObservableDictionary object can
be passed to the
ObservableDictionaryObserver.Register method in order to be
observed. This method accepts an
ObservableDictionary object (dictionary) as its
only parameter. This method then hooks up the event handlers in the
ObservableDictionaryObserver object to the events that can be raised by the
ObservableDictionary object passed in through the dictionary parameter. Therefore,
the following events and event handlers are bound together:
• The
ObservableDictionary.AddingEntry event is bound to the
ObservableDictionaryObserver.OnAddingListener event handler.
• The
ObservableDictionary.AddedEntry event is bound to the
ObservableDictionaryObserver.OnAddedListener event handler.
• The
ObservableDictionary.ChangingEntry event is bound to the

ObservableDictionaryObserver.OnChangingListener event handler.
• The
ObservableDictionary.ChangedEntry event is bound to the
ObservableDictionaryObserver.OnChangedListener event handler.
Observing Additions and Modifications to Dictionaries
|
341
The OnAddingListener and OnChangingListener methods watch for additions and
changes to the key/value pairs of the watched
ObservableDictionary object(s). Since
you have an event firing before and after an addition or modification occurs, you can
determine whether the addition or change should occur.
Two events are published by the
ObservableDictionaryObserver to allow for an exter-
nal entity to approve or deny the addition or changing of an entry. These events are
named
ApproveAdd and ApproveChange, respectively, and are of delegate type Approval
as shown below:
public delegate bool Approval(object sender,
ObservableDictionaryEventArgs<TKey,TValue> e);
This is where the keepChanges field of the ObservableDictionaryEventArgs object
comes into play. If an external source wants to block the addition or change, it can
simply return
false from its event handler implementation of the appropriate
Approve* event.
The
ObservableDictionaryObserver object will set this flag according to whether it
determines that the action should proceed or be prematurely terminated. The
ObservableDictionaryEventArgs object is passed back to the OnAdding and OnChanging
methods. These methods then return the value of the KeepChanges property to either

the calling
Add method or indexer. The Add method or indexer then uses this flag to
determine whether the internal dictionary object should be updated.
The code in Example 9-6 shows how to instantiate
ObservableDictionaries and
ObservableDictionaryObservers and how to register, set up approval, use, and unreg-
ister them.
Example 9-6. Using the ObservableDictionary and ObservableDictionaryObserver
classes
public static void TestObserverPattern( )
{
Dictionary<int, string> dictionary1 = new Dictionary<int, string>( );
Dictionary<int, string> dictionary2 = new Dictionary<int, string>( );
Dictionary<int, string> dictionary3 = new Dictionary<int, string>( );
// Create three observable dictionary instances
var obsDict1 = dictionary1.MakeObservableDictionary( );
var obsDict2 = dictionary2.MakeObservableDictionary( );
var obsDict3 = dictionary3.MakeObservableDictionary( );
// Create an observer for the three subject objects
var observer = new ObservableDictionaryObserver<int, string>( );
// Register the three subjects with the observer
observer.Register(obsDict1);
observer.Register(obsDict2);
observer.Register(obsDict3);
342
|
Chapter 9: Delegates, Events, and Lambda Expressions
// hook up the approval events for adding or changing
observer.ApproveAdd +=
new ObservableDictionaryObserver<int, string>.

Approval(SeekApproval);
observer.ApproveChange +=
new ObservableDictionaryObserver<int, string>.
Approval(SeekApproval);
// Use the observable instances
obsDict1.Add(1, "one");
obsDict2.Add(2, "two");
obsDict3.Add(3, "three");
// Insure the approval process worked
Debug.Assert(obsDict1.Count == 1);
Debug.Assert(obsDict2.Count == 1);
// this should be empty as the value was more than three characters
Debug.Assert(obsDict3.Count == 0);
// Unregister the observable instances
observer.Unregister(obsDict3);
observer.Unregister(obsDict2);
observer.Unregister(obsDict1);
///////////////////////////////////////////////////////////////
// Now do it with a different type of dictionary
///////////////////////////////////////////////////////////////
// Create two observable SortedList instances
SortedList<string, bool> sortedList1 = new SortedList<string, bool>( );
SortedList<string, bool> sortedList2 = new SortedList<string, bool>( );
var obsSortedList1 = sortedList1.MakeObservableDictionary( );
var obsSortedList2 = sortedList2.MakeObservableDictionary( );
// Create an observer for the two subject objects
ObservableDictionaryObserver<string, bool> listObserver =
new ObservableDictionaryObserver<string, bool>( );
// Register the three subjects with the observer
listObserver.Register(obsSortedList1);

listObserver.Register(obsSortedList2);
// hook up the approval events for adding or changing
listObserver.ApproveAdd +=
new ObservableDictionaryObserver<string, bool>.
Approval(ApprovePositive);
listObserver.ApproveChange +=
new ObservableDictionaryObserver<string, bool>.
Approval(ApprovePositive);
// Use the observable instances
Example 9-6. Using the ObservableDictionary and ObservableDictionaryObserver
classes (continued)
Observing Additions and Modifications to Dictionaries
|
343
Note that if the ObservableDictionaries are used without registering them, no events
will be raised. Since no events are raised, the observer cannot do its job, and values
may be added to the unregistered subjects that are out of bounds for the application.
When using the observer design pattern in this fashion, keep in mind that fine-
grained events, such as the ones in this recipe, could possibly drag down perfor-
mance, so set a goal and then profile your code. If you have many subjects raising
many events, your application could fail to meet performance expectations.
Notice that in the second set of code exercising the
ObservableDictionary,a
SortedList<K,V> is used instead of a Dictionary<K,V> with no difference in the usage
patterns or results:
// Use Dictionary<int,string> as the base
Dictionary<int, string> dictionary1 = new Dictionary<int, string>( );
var obsDict1 = dictionary1.MakeObservableDictionary( );
// Use SortedList<string,bool> as the base
SortedList<string, bool> sortedList1 = new SortedList<string, bool>( );

var obsSortedList1 = sortedList1.MakeObservableDictionary( );
obsSortedList1.Add("Item 1",true);
obsSortedList2.Add("Item 2", false);
// Insure the approval process worked
Debug.Assert(obsSortedList1.Count == 1);
// this should be empty as only true values are taken
Debug.Assert(obsSortedList2.Count == 0);
// Unregister the observable instances
listObserver.Unregister(obsSortedList2);
listObserver.Unregister(obsSortedList1);
}
static bool SeekApproval(object sender,
ObservableDictionaryEventArgs<int, string> args)
{
// only allow strings of no more than 3 characters in
// our dictionary
string value = args.Value.ToString( );
if (value.Length <= 3)
return true;
return false;
}
static bool ApprovePositive(object sender,
ObservableDictionaryEventArgs<string, bool> args)
{
// only allow positive values
return args.Value;
}
Example 9-6. Using the ObservableDictionary and ObservableDictionaryObserver
classes (continued)
344

|
Chapter 9: Delegates, Events, and Lambda Expressions
See Also
The “Event Keyword,” “EventHandler Delegate,” “EventArgs Class,” and “Han-
dling and Raising Events” topics in the MSDN documentation.
9.7 Using Lambda Expressions
Problem
There is a feature in C# 3.0 called lambda expressions. While lambda expressions
can be viewed as syntactic sugar for making anonymous method definition less diffi-
cult, you want to understand all of the different ways that they can be used to help
you in your daily programming chores as well as understand the ramifications of
those uses.
Solution
Lambda expressions can be implemented by the compiler from methods created by
the developer. There are two orthogonal characteristics that lambda expressions may
have:
• Parameter lists may have explicit or implicit types.
• Bodies may be expressions or statement blocks.
Let’s start with the original way to use delegates. First, you would declare a delegate
type,
DoWork in this case, and then you would create an instance of it (as shown here
in the
WorkItOut method). Declaring the instance of the delegate requires that you
specify a method to execute when the delegate is invoked, and here the
DoWorkMethodImpl method has been connected. The delegate is invoked, and the text
is written to the console via the
DoWorkMethodImpl method:
class OldWay
{
// declare delegate

delegate int DoWork(string work);
// have a method to create an instance of and call the delegate
public void WorkItOut( )
{
// declare instance
DoWork dw = new DoWork(DoWorkMethodImpl);
// invoke delegate
int i = dw("Do work the old way");
}
// Have a method that the delegate is tied to with a matching signature
// so that it is invoked when the delegate is called
public int DoWorkMethodImpl(string s)
{
Using Lambda Expressions
|
345
Console.WriteLine(s);
return s.GetHashCode( );
}
}
Lambda expressions allow you to set up code to run when a delegate is invoked, but
there does not need to be a named formal method declaration that is given to the del-
egate. The method thus declared is nameless and closed over the scope of the outer
method. For example, you could have written the preceding code using a lambda
expression such as this:
class LambdaWay
{
// declare delegate
delegate int DoWork(string work);
// have a method to create an instance of and call the delegate

public void WorkItOut( )
{
// declare instance
DoWork dw = s =>
{
Console.WriteLine(s);
return s.GetHashCode( );
};
// invoke delegate
int i = dw("Do some inline work");
}
}
Notice that instead of having a method called DoWorkMethodImpl, you use the => oper-
ator to directly assign the code from that method inline to the
DoWork delegate. The
assignment looks like this:
DoWork dw = s =>
{
Console.WriteLine(s);
return s.GetHashCode( );
};
You also provide the parameter required by the DoWork delegate (string), and your
code returns an int (
s.GetHashCode( )) as the delegate requires. When setting up a
lambda expression, the code must “match” the delegate signature, or you will get a
compiler error.
By match we mean:
If explicitly typed, the lambda parameters must exactly match the delegate parame-
ters. If implicitly typed, the lambda parameters get the delegate parameter types.
The body of the lambda must be a legal expression or statement block given the

parameter types.
346
|
Chapter 9: Delegates, Events, and Lambda Expressions
The return type of the lambda must be implicitly convertible to the return type of the
delegate. It need not match exactly.
There is yet another way you can set up the delegate, and that is through the magic
of delegate inference. Delegate inference allows you to assign the method name
directly to the delegate instance without having to write the code for creating a new
delegate object. Under the covers, C# actually writes the IL for creating the delegate
object, but you don’t have to do it explicitly here. Using delegate inference instead of
writing out
new [Delegate Type]([Method Name]) everywhere helps to unclutter the
code involved in the usage of delegates, as shown here:
class DirectAssignmentWay
{
// declare delegate
delegate int DoWork(string work);
// have a method to create an instance of and call the delegate
public void WorkItOut( )
{
// declare instance and assign method
DoWork dw = DoWorkMethodImpl;
// invoke delegate
int i = dw("Do some direct assignment work");
}
// Have a method that the delegate is tied to with a matching signature
// so that it is invoked when the delegate is called
public int DoWorkMethodImpl(string s)
{

Console.WriteLine(s);
return s.GetHashCode( );
}
}
Notice that all that is assigned to the DoWork delegate instance dw is the method name
DoWorkMethodImpl. There is no “new DoWork(DoWorkMethodImpl)” call as there was in
older C# code.
Remember, the underlying delegate wrapper does not go away; dele-
gate inference just simplifies the syntax a bit by hiding some of it.
Alternatively, you can also set up lambda expressions that take generic type parame-
ters to enable working with generic delegates as you do here in the
GenericWay class:
class GenericWay
{
// have a method to create two instances of and call the delegates
public void WorkItOut( )
{
Func<string,string> dwString = s =>
{
Using Lambda Expressions
|
347
Console.WriteLine(s);
return s;
};
// invoke string delegate
string retStr = dwString("Do some generic work");
Func<int,int> dwInt = i =>
{
Console.WriteLine(i);

return i;
};
// invoke int delegate
int j = dwInt(5);
}
}
Discussion
One of the useful things about lambda expressions is the concept of outer variables.
The official definition of outer variables is that they are any local variable, value
parameter, or parameter array with a scope that contains the lambda expression.
What does this mean? It means that, inside of the code of the lambda expression,
you can touch variables outside of the scope of that method. There is a concept of
“capturing” the variables that occurs when a lambda expression actually makes refer-
ence to one of the outer variables. In the following example, the
count variable is cap-
tured and incremented by the lambda expression. The
count variable is not part of
the original scope of the lambda expression but part of the outer scope. It is incre-
mented and then the incremented value is returned and totaled:
public void SeeOuterWork( )
{
int count = 0;
int total = 0;
Func<int> countUp = ( ) => count++;
for(int i=0;i<10;i++)
{
total += countUp( );
}
Debug.WriteLine("Total = " + total);
}

What capturing actually does is extend the lifetime of the outer variable to coincide
with the lifetime of the underlying delegate instance that represents the lambda
expression. This should encourage you to be careful about what you touch from
inside a lambda expression. You could be causing things to hang around a lot longer
than you originally planned. The garbage collector won’t get a chance to clean up
those outer variables until later, when they are used in the lambda expression. Cap-
turing outer variables has another garbage-collector effect: when locals or value
348
|
Chapter 9: Delegates, Events, and Lambda Expressions
parameters are captured, they are no longer considered to be fixed but are now mov-
able, so any unsafe code must now fix that variable before use by using the
fixed
keyword.
Outer variables can affect how the compiler generates the internal IL for the lambda
expression. If it uses outer variables, it is generated as a private method of a nested
class rather than as another private method of the class it is declared in, as it other-
wise would be. If the outer method is static, then the lambda expression cannot
access instance members via the “this” keyword, as the nested class will also be gen-
erated as static.
There are two types of lambda expressions: Expression lambdas and Statement
lambdas. This Expression lambda has no parameters and simply increments the
count variable in an expression:
int count = 0;
Count countUp = ( ) => count++;
Statement lambdas have the body enclosed in curly braces and can contain any num-
ber of statements like this:
Func<int,int> dwInt = i =>
{
Console.WriteLine(i);

return i;
};
A few last things to remember about lambda expressions:
• They can’t use
break, goto,orcontinue to jump from the lambda expression to a
target outside the lambda expression block.
• No unsafe code can be executed inside a lambda expression.
• Lambda expressions cannot be used on the left side of the
is operator.
• Since lambda expressions are a superset of anonymous methods, all restrictions
that apply to anonymous methods also apply to lambda expressions.
See Also
The “Lambda Expressions (C# Programming Guide)” topic in the MSDN
documentation.
9.8 Set Up Event Handlers Without the Mess
Problem
In versions of the .NET Framework prior to 2.0, the System.EventHandler delegate
could be used on events in which the arguments were always of type
System.
EventArgs
. This was great if you really didn’t care about any data that went along
Set Up Event Handlers Without the Mess
|
349
with an event. But as you are all fine programmers and can see the possibilities of
passing data along with the event, you had to set up a delegate and an event for every
event you wanted. Example 9-7 demonstrates an old newspaper class that sends
news to subscribers using the pre NET 2.0 event and event-handling methodology.
Example 9-7. Using pre NET 2.0 event and event-handling methods
public class IWantToKnowThen

{
public static void TryMe( )
{
OldNewspaper DailyPaperFlash = new OldNewspaper( );
DailyPaperFlash.NewsEvent +=
new OldNewspaper.NewsEventHandler(StaleNews);
// send news
DailyPaperFlash.TransmitStaleNews("Patriots win third super
bowl!");
DailyPaperFlash.TransmitStaleNews("W takes office amongst recount.
");
DailyPaperFlash.TransmitStaleNews("VS2005 is sooo passe");
}
private static void StaleNews(object src, NewsEventArgs nea)
{
Console.WriteLine(nea.LatestNews);
}
}
// EventArgs derived class to hold our news data
public class NewsEventArgs : EventArgs
{
private string _latestNews;
public NewsEventArgs(string latestNews)
{
_latestNews = latestNews;
}
public string LatestNews
{
get { return _latestNews; }
}

}
// OldNewspaper class
public class OldNewspaper
{
// Allow clients to get the news.
public delegate void NewsEventHandler(Object sender, NewsEventArgs e);
public event NewsEventHandler NewsEvent;
// Provide nice wrapper for sending news to clients.
public void TransmitStaleNews(string news)
350
|
Chapter 9: Delegates, Events, and Lambda Expressions
This code sets up an event that will report the news to subscribers as it comes in. It
passes them the news data as an argument of type
NewsEventArgs that has a
LatestNews property.
As you can see from this example, whenever you had to set up multiple event han-
dlers, it became an exercise in copy-and-paste and changing the event argument class
type over and over again. It would be nice to not have to define lots of delegates and
events just to change the event arguments, as all events (and corresponding han-
dlers) are supposed to look like this:
void [EventHandler](object sender, [EventArgs] args)
{
// Do something about this event firing.
}
Solution
EventHandler<T> takes a type parameter that represents the type of the System.
EventArgs
derived class to use in your event handlers. The beauty of this is that you
no longer have to keep creating a delegate and an event for every event you wish to

publish from your class. Even better, the Framework only has to have one event dele-
gate instead of one for every event that passes custom data! Using the example
shown in the Problem section, you can now rewrite the declaration of the event han-
dler like this:
// Old way
public delegate void NewsEventHandler(Object sender, NewsEventArgs e);
public event NewsEventHandler NewsEvent;
// New way
public event EventHandler<NewsEventArgs> NewsEvent;
Now, you set up the nice wrapper function to allow the user to easily trigger the
event:
// Old way
public void TransmitNews(string news)
{
// Copy to a temporary variable to be thread-safe.
NewsEventHandler newsEvent = NewsEvent;
if (newsEvent != null)
newsEvent(this, new NewsEventArgs(news));
}
{
// Copy to a temporary variable to be thread-safe.
NewsEventHandler newsEvent = NewsEvent;
if (newsEvent != null)
newsEvent(this, new NewsEventArgs(news));
}
}
Example 9-7. Using pre NET 2.0 event and event-handling methods (continued)
Set Up Event Handlers Without the Mess
|
351

// New way
public void TransmitNews(string news)
{
// Copy to a temporary variable to be thread-safe.
EventHandler<NewsEventArgs> breakingNews = NewsEvent;
if (breakingNews != null)
breakingNews(this, new NewsEventArgs(news));
}
The client can then hook up to the OldNewspaper class like this:
// Old way
public class IWantToKnowThen
{
public static void TryMe( )
{
OldNewspaper DailyPaperFlash = new OldNewspaper( );
DailyPaperFlash.NewsEvent +=
new OldNewspaper.NewsEventHandler(StaleNews);
// send news
DailyPaperFlash.TransmitStaleNews("Patriots win third super bowl!");
DailyPaperFlash.TransmitStaleNews("W takes office amongst recount.");
DailyPaperFlash.TransmitStaleNews("VS2005 is sooo passe");
}
private static void StaleNews(object src, NewsEventArgs nea)
{
Console.WriteLine(nea.LatestNews);
}
}
// New way
public class IWantToKnowNow
{

public static void Test( )
{
eNewspaper DailyBitFlash = new eNewspaper( );
DailyBitFlash.NewsEvent +=
new EventHandler<NewsEventArgs>(BreakingNews);
// send breaking news
DailyBitFlash.TransmitBreakingNews("Patriots win!");
DailyBitFlash.TransmitBreakingNews("New pres coming in 08.");
DailyBitFlash.TransmitBreakingNews("VS2008 & .NET 3.5 Rocks LA");
}
private static void BreakingNews(object src, NewsEventArgs nea)
{
Console.WriteLine(nea.LatestNews);
}
}

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

Tải bản đầy đủ ngay
×