讲故事谈.NET委托:一个C#睡前故事

讲故事谈.NET委托:一个C#睡前故事


英文版原作者:Chris Sells(http:
// www.sellsbrothers.com/writing/default.aspx?content=delegates.htm)
翻译:袁晓辉(www.farproc.com http: // blog.csdn.net/uoyevoli) 



The following 
is  an excerpt from Windows Forms  2.0  Programming, Chris Sells  &  Michael Weinhardt, Addison - Wesley,  2006 . It ' s been updated from the original version for C# 2.0.

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  {
  Boss boss;
 
  
public void Advise(Boss boss) {
    
this.boss = boss;
  }

 
  
public void DoWork() {
    Console.WriteLine(
"Worker: work started");
    
ifthis.boss != null ) this.boss.WorkStarted();
 
    Console.WriteLine(
"Worker: work progressing");
    
ifthis.boss != null ) this.boss.WorkProgressing();
 
    Console.WriteLine(
"Worker: work completed");
    
ifthis.boss != null ) {
      
int grade = this.boss.WorkCompleted();
      Console.WriteLine(
"Worker grade= {0}", grade);
    }

  }

}

 
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  {
  IWorkerEvents events;
 
  
public void Advise(IWorkerEvents events) {
    
this.events = events;
  }

 
  
public void DoWork() {
    Console.WriteLine(
"Worker: work started");
    
ifthis.events != null ) this.events.WorkStarted();
 
    Console.WriteLine(
"Worker: work progressing");
    
ifthis.events != null ) this.events.WorkProgressing();
 
    Console.WriteLine(
"Worker: work completed");
    
ifthis.events!= null ) {
      
int grade = this.events.WorkCompleted();
      Console.WriteLine(
"Worker grade= {0}", grade);
    }

  }

}

 
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 WorkStarted Started;
  
public WorkProgressing Progressing;
  
public WorkCompleted Completed;
 
  
public void DoWork() {
    Console.WriteLine(
"Worker: work started");
    
ifthis.Started != null ) this.Started();
 
    Console.WriteLine(
"Worker: work progressing");
    
ifthis.Progressing != null ) this.Progressing();
 
    Console.WriteLine(
"Worker: work completed");
    
ifthis.Completed != null ) {
      
int grade = this.Completed();
      Console.WriteLine(
"Worker grade= {0}", grade);
    }

  }

}

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

}

 
class  Universe  {
  
static void Main() {
    Worker peter 
= new Worker();
    Boss boss 
= new Boss();
 
    
// NOTE: We've replaced the Advise method with the assignment operation
    peter.Completed = new WorkCompleted(boss.WorkCompleted);
    peter.DoWork();
 
    Console.WriteLine(
"Main: worker completed work");
    Console.ReadLine();
  }

}


And, because Peter was under so much pressure, he decided to advantage of the shorthand notation 
for  assigning delegates provided by C#  2.0 :

class  Universe  {
  
static void Main() {
    ...
    peter.Completed 
= boss.WorkCompleted;
    ...
  }

}

Static Listeners

Delegates 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-encompassing 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 
= boss.WorkCompleted;
    peter.Started 
= WorkerStartedWork;
    peter.Completed 
= WorkerCompletedWork; // Oops!
    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 ' 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 clients to add or remove themselves ( using  the  +=  and  -=  operators  in  C#), forcing his boss and the universe to play nicely:

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

    peter.Completed 
= boss.WorkCompleted; // ERR!
    peter.Completed += boss.WorkCompleted; // OK
    peter.Started += Universe.WorkerStartedWork; // OK
    peter.Completed += Universe.WorkerCompletedWork; // OK

    peter.DoWork();

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

}

Harvesting All Results

At 
this  point, Peter breathes a sigh 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:

class  Worker  {
  ...
  
public void DoWork() {
    ...
    Console.WriteLine(
"Worker: work completed");
 
    
ifthis.Completed != null ) {
      
foreach( WorkCompleted wc in this.Completed.GetInvocationList() ) {
        
int grade = wc();
        Console.WriteLine(
"Worker grade= {0}", grade);
      }

    }

  }

}

 
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);
    }

  }

}

Asynchronous Notification: Fire 
&  Forget

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

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

}


class  Universe  {
  ...
  
static int WorkerCompletedWork() {
    System.Threading.Thread.Sleep(
1000000);
    Console.WriteLine(
"Universe 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:

class  Worker  {
  ...
  
public void DoWork() {
    ...
    Console.WriteLine(
"Worker: work completed");
    
ifthis.Completed != null ) {
      
foreach( WorkCompleted wc in this.Completed.GetInvocationList() ) {
        wc.BeginInvoke(
nullnull); // EndInvoke call required by .NET
      }

    }

  }

}

Asynchronous Notification: Polling

The call to BeginInvoke 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). Plus, he�s afraid he�s leaking .NET resources acquired by calling BeginInvoke without calling the corresponding EndInvoke method, so, he fires the  event  asynchronously, but polls periodically, looking  for  the grade to be available:

class  Worker  {
  ...
  
public void DoWork() {
    ...
    Console.WriteLine(
"Worker: work completed");
    
ifthis.Completed != null ) {
      
foreach( WorkCompleted wc in this.Completed.GetInvocationList() ) {
        IAsyncResult result 
= wc.BeginInvoke(nullnull);
        
while!result.IsCompleted ) System.Threading.Thread.Sleep(1);
        
int grade = wc.EndInvoke(result);
       Console.WriteLine(
"Worker grade= {0}", grade);
      }

    }

  }

}

Asynchronous 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 asynchronous work has completed, allowing him to  get  back to work immediately, but still be notified when his work has been graded:

class  Worker  {
  ...
  
public void DoWork() {
    ...
    Console.WriteLine(
"Worker: work completed");
    
ifthis.Completed != null ) {
      
foreach( WorkCompleted wc in this.Completed.GetInvocationList() ) {
        wc.BeginInvoke(
this.WorkGraded, wc);
      }

    }

  }


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

}

Anonymous Delegates

At 
this  point, Peter  is   using  delegates to notify interested parties  in  the process of his work and  using  delegates to  get  notified when grades are available on the work he�s completed. The delegates provided by his boss and the universe are provided by separate entities, so it makes sense that they are encapsulated  in  methods on those entities. However,  in  the  case  of the WorkGraded method, there�s really no good reason  for   this  to be a separate method except the syntactic requirements of C#  1.0 . As of C#  2.0 , Peter can drop the code required to handle the processing of his work grade into an anonymous  delegate :

class  Worker  {
  ...
  
public void DoWork() {
    ...
    Console.WriteLine(
"Worker: work completed");
    
ifthis.Completed != null ) {
      
foreach( WorkCompleted wc in this.Completed.GetInvocationList() ) {
        wc.BeginInvoke(
delegate(IAsyncResult result) {
          WorkCompleted wc2 
= (WorkCompleted)result.AsyncState;
          
int grade = wc2.EndInvoke(result);
          Console.WriteLine(
"Worker grade= {0}", grade);
        }
,
        wc);
      }

    }

  }

}


Here, instead of passing 
in  the name of a method to call when his work has been graded, he�s passing  in  the body of the method itself  as  designated with a different use of the  delegate  keyword to create a method with no name (and therefore �anonymous�). The body of the method  is  fundamentally the same  in  that Peter still passes the WorkCompleted  delegate   as  a parameter to BeginInvoke and then pulls it  out  of AsyncState  for  use  in  extracting the result. However, one of the benefits of anonymous delegates that Peter knows  is  that he can make use of the variables  in  the surrounding context from within the anonymous  delegate  body, causing him to rewrite his code thusly:

class  Worker  {
  ...
  
public void DoWork() {
    ...
    Console.WriteLine(
"Worker: work completed");
    
ifthis.Completed != null ) {
      
foreach( WorkCompleted wc in this.Completed.GetInvocationList() ) {
        wc.BeginInvoke(
delegate(IAsyncResult result) {
          
// Use wc variable from surrounding context (ERR!)
          int grade = wc.EndInvoke(result);
          Console.WriteLine(
"Worker grade= {0}", grade);
        }
,
        
null);
      }

    }

  }

}


This code compiles just fine, but when it�s run, it will cause the following exception to be thrown:

System.InvalidOperationException:
The IAsyncResult 
object  provided does not match  this   delegate .

The problem 
is  that  while  the wc variable  is  allowed to be used  in  the anonymous  delegate , it�s still being used by the  for - each statement. As soon  as  the asynchronous invocation begins, the wc variable changes and the  delegate  used to start things (wc) no longer matches the async result passed  as  an argument to the anonymous  delegate . Peter slaps his head and creates a hybrid solution:

class  Worker  {
  ...
  
public void DoWork() {
    ...
    Console.WriteLine(
"Worker: work completed");
    
ifthis.Completed != null ) {
      
foreach( WorkCompleted wc in this.Completed.GetInvocationList() ) {
        
// Create an unchanging variable referencing the current delegate
        WorkCompleted wc2 = wc;
        wc.BeginInvoke(
delegate(IAsyncResult result) {
          
// Use wc2 variable from surrounding context
          int grade = wc2.EndInvoke(result);
          Console.WriteLine(
"Worker grade= {0}", grade);
        }
,
        
null);
      }

    }

  }

}

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

  }

}

 
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 and handling them using anonymous delegates, resulting in the following complete solution:

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

class  Worker  {
  
public event WorkStarted Started;
  
public event WorkProgressing Progressing;
  
public event WorkCompleted Completed;
 
  
public void DoWork() {
    Console.WriteLine(
"Worker: work started");
    
ifthis.Started != null )
      
this.Started();
 
    Console.WriteLine(
"Worker: work progressing");
    
ifthis.Progressing != null )
      
this.Progressing();
 
    Console.WriteLine(
"Worker: work completed");
    
ifthis.Completed != null ) {
      
foreach( WorkCompleted wc in this.Completed.GetInvocationList() ) {
        WorkCompleted wc2 
= wc;
        wc.BeginInvoke(
delegate(IAsyncResult result) {
          
int grade = wc2.EndInvoke(result);
          Console.WriteLine(
"Worker grade= {0}", grade);
        }
,
        
null);
      }

    }

  }

}


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

}


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


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


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

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

}


Peter knows that getting results asynchronously comes with issues, 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.

And they all lived happily every after.

The end
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值