.NET Delegates: A C# Bedtime Story

Tight Coupling

Once upon a time, in a strange land south of here, there was a worker named Peter. He was a diligent worker who would readily accept requests from his boss. However, his boss was a mean, untrusting man who insisted on steady progress reports. Since Peter did not want his boss standing in his office looking over his shoulder, Peter promised to notify his boss whenever his work progressed. Peter implemented this promise by periodically calling his boss back via a typed reference like so:

class  Worker 
{
    
public   void  Advise(Boss boss) 
    { 
        _boss 
=  boss; 
    }

    
public   void  DoWork() 
    {
        Console.WriteLine(
" Worker: work started " );
        
if ( _boss  !=   null  )
        {
            _boss.WorkStarted();
        }

        Console.WriteLine(
" Worker: work progressing " );
        
if ( _boss  !=   null  )
        {
            _boss.WorkProgressing();
        }

        Console.WriteLine(
" Worker: work completed " );
        
if ( _boss  !=   null  ) 
        {
            
int  grade  =  _boss.WorkCompleted();
            Console.WriteLine(
" Worker grade=  "   +  grade);
        }
    }
    
private  Boss _boss;
}

class  Boss 
{
    
public   void  WorkStarted()
    {
        
// boss doesn't care.
    }

    
public   void  WorkProgressing() 
    {
        
// boss doesn't care.
    }

    
public   int  WorkCompleted() 
    {
        Console.WriteLine(
" It's about time! " );
        
return   2 // out of 10
    }
}

class  Universe 
{
    
static   void  Main() 
    {
        Worker peter 
=   new  Worker();
        Boss boss 
=   new  Boss();
        peter.Advise(boss);
        peter.DoWork();

        Console.WriteLine(
" Main: worker completed work " );
        Console.ReadLine();
    }
}

Interfaces

Now Peter was a special person. Not only was he able to put up with his mean-spirited boss, but he also had a deep connection with the universe around him. So much so that he felt that the universe was interested in his progress. Unfortunately, there was no way for Peter to advise the Universe of his progress unless he added a special Advise method and special callbacks just for the Universe, in addition to keeping his boss informed. What Peter really wanted to do was to separate the list of potential notifications from the implementation of those notification methods. And so he decided to split the methods into an interface:

interface  IWorkerEvents 
{
    
void  WorkStarted();
    
void  WorkProgressing();
    
int  WorkCompleted();
}

class  Worker 
{
    
public   void  Advise(IWorkerEvents events) 
    { 
        _events 
=  events; 
    }

    
public   void  DoWork() 
    {
        Console.WriteLine(
" Worker: work started " );
        
if ( _events  !=   null  )
        {
            _events.WorkStarted();
        }

        Console.WriteLine(
" Worker: work progressing " );
        
if (_events  !=   null  )
        {
            _events.WorkProgressing();
        }

        Console.WriteLine(
" Worker: work completed " );
        
if (_events  !=   null  ) 
        {
            
int  grade  =  _events.WorkCompleted();
            Console.WriteLine(
" Worker grade=  "   +  grade);
        }
    }
    
private  IWorkerEvents _events;
}

class  Boss : IWorkerEvents 
{
    
public   void  WorkStarted()
    { 
        
// boss doesn't care.
    }

    
public   void  WorkProgressing() 
    { 
        
// boss doesn't care.
    }

    
public   int  WorkCompleted() 
    {
        Console.WriteLine(
" It's about time! " );
        
return   3 // out of 10
    }
}

Delegates

Unfortunately, Peter was so busy talking his boss into implementing this interface that he didn't get around to notifying the Universe, but he knew he would soon. At least he'd abstracted the reference of his boss far away from him so that others who implemented the IWorkerEvents interface could be notified of his work progress.

Still, his boss complained bitterly. "Peter!" his boss fumed. "Why are you bothering to notify me when you start your work or when your work is progressing?!? I don't care about those events. Not only do you force me to implement those methods, but you're wasting valuable work time waiting for me to return from the event, which is further expanded when I am far away! Can't you figure out a way to stop bothering me?"

And so, Peter decided that while interfaces were useful for many things, when it came to events, their granularity was not fine enough. He wished to be able to notify interested parties only of the events that matched their hearts' desires. So, he decided to break the methods out of the interface into separate delegate functions, each of which acted like a little tiny interface of one method each:

delegate   void  WorkStarted();
delegate   void  WorkProgressing();
delegate   int  WorkCompleted();

class  Worker 
{
    
public   void  DoWork() 
    {
        Console.WriteLine(
" Worker: work started " );
        
if ( started  !=   null  )
        {
            started();
        }

        Console.WriteLine(
" Worker: work progressing " );
        
if ( progressing  !=   null  )
        {
            progressing();
        }

        Console.WriteLine(
" Worker: work completed " );
        
if ( completed  !=   null  ) 
        {
            
int  grade  =  completed();
            Console.WriteLine(
" Worker grade=  "   +  grade);
        }
    }
    
public  WorkStarted started;
    
public  WorkProgressing progressing;
    
public  WorkCompleted completed;
}

class  Boss 
{
    
public   int  WorkCompleted() 
    {
        Console.WriteLine(
" Better...... " );
        
return   4 // out of 10
    }
}

class  Universe 
{
    
static   void  Main() 
    {
        Worker peter 
=   new  Worker();
        Boss boss 
=   new  Boss();
        peter.completed 
=   new  WorkCompleted(boss.WorkCompleted);
        peter.DoWork();

        Console.WriteLine(
" Main: worker completed work " );
        Console.ReadLine();
    }


Static Listeners

This accomplished the goal of not bothering his boss with events that he didn't want, but still Peter had not managed to get the universe on his list of listeners. Since the universe is an all-compassing entity, it didn't seem right to hook delegates to instance members (imagine how many resources multiple instances of the universe would need...). Instead, Peter need to hook delegates to static members, which delegates support fully:

class  Universe 
{
    
static   void  WorkerStartedWork() 
    {
        Console.WriteLine(
" Universe notices worker starting work " );
    }

    
static   int  WorkerCompletedWork() 
    {
        Console.WriteLine(
" Universe pleased with worker's work " );
        
return   7 ;
    }

    
static   void  Main() 
    {
        Worker peter 
=   new  Worker();
        Boss boss 
=   new  Boss();
        peter.completed 
=   new  WorkCompleted(boss.WorkCompleted);
        peter.started 
=   new  WorkStarted(Universe.WorkerStartedWork);
        peter.completed 
=   new  WorkCompleted(Universe.WorkerCompletedWork);
        peter.DoWork();

        Console.WriteLine(
" Main: worker completed work " );
        Console.ReadLine();
    }
}

Events

Unfortunately, the Universe being very busy and unaccustomed to paying attention to individuals, has managed to replace Peter's boss's delegate with its own. This is an unintended side effect of making the delegate fields public in Peter's Worker class. Likewise, if Peter's boss gets impatient, he can decide to fire Peter's delegates himself (which is just the kind of rude thing that Peter's boss was apt to do):

 

//  Peter's boss taking matters into his own hands
if ( peter.completed  !=   null  )
{
    peter.completed();
}

Peter wants to make sure that neither of these can happens. He realizes he needs to add registration and unregistration functions for each delegate so that listeners can add or remove themselves, but can't clear the entire list or fire Peter's events. Instead of implementing these functions himself, Peter uses the event keyword to make the C# compiler build these methods for him:

class  Worker 
{
    ......
    
public   event  WorkStarted started;
    
public   event  WorkProgressing progressing;
    
public   event  WorkCompleted completed;
}

Peter knows that the event keyword erects a property around a delegate, only allowing C# clients to add or remove themselves with the += and -= operators, forcing his boss and the universe to play nicely:

static   void  Main() 
{
    Worker peter 
=   new  Worker();
    Boss boss 
=   new  Boss();
    peter.completed 
+=   new  WorkCompleted(boss.WorkCompleted);
    peter.started 
+=   new  WorkStarted(Universe.WorkerStartedWork);
    peter.completed 
+=   new  WorkCompleted(Universe.WorkerCompletedWork);
    peter.DoWork();

    Console.WriteLine(
" Main: worker completed work " );
    Console.ReadLine();
}

Harvesting All Results

At this point, Peter breathes a sign of relief. He has managed to satisfy the requirements of all his listeners without having to be closely coupled with the specific implementations. However, he notices that while both his boss and the universe provide grades of his work that he's only receiving one of the grades. In the face of multiple listeners, he'd really like to harvest all of their results. So, he reaches into his delegate and pulls out the list of listeners so that he can call each of them manually:

public   void  DoWork() 
{
    ......
    Console.WriteLine(
" Worker: work completed " );
    
if ( completed  !=   null  ) 
    {
    
foreach ( WorkCompleted wc  in  completed.GetInvocationList() ) 
    {
    
int  grade  =  wc();
    Console.WriteLine(
" Worker grade=  "   +  grade);
    }
}

Async Notification: Fire & Forget

In the meantime, his boss and the universe have been distracted with other things, which means that the time it takes them to grade Peter's work is greatly expanded:

class  Boss 
{
    
public   int  WorkCompleted() 
    {
        System.Threading.Thread.Sleep(
3000 );
        Console.WriteLine(
" Better...... " );  return   6 /*  out of 10  */
    }
}

class  Universe 
{
    
static   int  WorkerCompletedWork() 
    {
        System.Threading.Thread.Sleep(
4000 );
        Console.WriteLine(
" Universe is pleased with worker's work " );
        
return   7 ;
    }
    ......
}

Unfortunately, since Peter is notifying each listener one at a time, waiting for each to grade him, these notifications now take up quite a bit of his time when he should be working. So, he decides to forget the grade and just fire the event asynchronously:

     public   void  DoWork() 
    {
        ......
        Console.WriteLine(
" Worker: work completed " );
        
if ( completed  !=   null  ) 
        {
            
foreach ( WorkCompleted wc  in  completed.GetInvocationList() )
            {
                wc.BeginInvoke(
null null );
            }
        }
    }

Async Notification: Polling

This allows Peter to notify the listeners while letting Peter get back to work immediately, letting the process thread pool invoke the delegate. Over time, however, Peter finds that he misses the feedback on his work. He knows that he does a good job and appreciates the praise of the universe as a whole (if not his boss specifically). So, he fires the event asynchronously, but polls periodically, looking for the grade to be available:

     public   void  DoWork() 
    {
        ......
        Console.WriteLine(
" Worker: work completed " );
        
if ( completed  !=   null  ) 
        {
            
foreach ( WorkCompleted wc  in  completed.GetInvocationList() ) 
            {
                IAsyncResult res 
=  wc.BeginInvoke( null null );
                
while ! res.IsCompleted ) System.Threading.Thread.Sleep( 1 );
                
int  grade  =  wc.EndInvoke(res);
                Console.WriteLine(
" Worker grade=  "   +  grade);
            }
        }
    }

Async Notification: Delegates

Unfortunately, Peter is back to what he wanted his boss to avoid with him in the beginning, i.e. looking over the shoulder of the entity doing the work. So, he decides to employ his own delegate as a means of notification when the async delegate has completed, allowing him to get back to work immediately, but still be notified when his work has been graded:

     public   void  DoWork() 
    {
        ......
        Console.WriteLine(
" Worker: work completed " );
        
if ( completed  !=   null  ) 
        {
            
foreach ( WorkCompleted wc  in  completed.GetInvocationList() ) 
            {
                wc.BeginInvoke(
new  AsyncCallback(WorkGraded), wc);
            }
        }
    }

    
private   void  WorkGraded(IAsyncResult res) 
    {
        WorkCompleted wc 
=  (WorkCompleted)res.AsyncState;
        
int  grade  =  wc.EndInvoke(res);
        Console.WriteLine(
" Worker grade=  "   +  grade);
    }

Happiness in the Universe

Peter, his boss and the universe are finally satisfied. Peter's boss and the universe are allowed to be notified of the events that interest them, reducing the burden of implementation and the cost of unnecessary round-trips. Peter can notify them each, ignoring how long it takes them to return from their target methods, while still getting his results asynchronously. Peter knows that it's not quite that easy, because as soon as he fires events asynchronously, the target methods are likely to be executed on another thread, as is Peter's notification of when the target method has completed. However, Peter is good friends with Mike, who is very familiar with threading issues and can provide guidance in that area.


 

你可能感兴趣的:(delegate)