Delegation and events-a fictitious story

Source: Internet
Author: User
This article is excerpted from "Windows Forms programming" published by People's post and telecommunications Publishing House (translated by Chris Sells and translated by Jiang xianzhe ). The mechanisms and Applications of delegation and events in C #/. NET are explained through a vivid fictional story.

1. Delegate

Once upon a time, in a foreign country in the South, there was Peter's hardworking worker who was very familiar with his boss. However, his boss was a mean suspect, he insisted that Peter keep reporting the progress. Because Peter does not want to be stared at by the boss, he promises to report the progress to the boss at any time. Peter regularly calls back the boss through the following typed reference:

Class Worker {
Public void Advise (Boss boss) {this. 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 );
}
}
Boss boss;
}

Class Boss {
Public void WorkStarted () {/* boss does not care */}
Public void WorkProgressing () {/* boss does not care */}
Public int WorkCompleted (){
Console. WriteLine ("It's about time! ");
Return 2;/* 10 points or less */
}
}

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

1.1 Interface

Now, Peter has become a special character. He not only can endure the mean boss, but also has a close relationship with the world around him (universe. Peter felt that universe was equally interested in his work processes. Unfortunately, if you do not add a special Advise method and special callback for universe, Peter cannot notify universe of the work progress in addition to ensuring that the boss can be notified. Peter wants to separate the list of potential notifications from the implementation of those notification methods. To this end, he decided to separate the methods into an interface:

Interface IWorkerEvents {
Void WorkStarted ();
Void WorkProgressing ();
Int WorkCompleted ();
}

Class Worker {
Public void Advise (IWorkerEvents events) {this. 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 );
}
}
IWorkerEvents events;
}

Class Boss: IWorkerEvents {
Public void WorkStarted () {/* boss does not care */}
Public void WorkProgressing () {/* boss does not care */}
Public int WorkCompleted (){
Console. WriteLine ("It's about time! ");
Return 3;/* 10 points or less */
}
}

1.2 Commission

Unfortunately, Peter was so busy persuading boss to implement this interface that he did not care about notifying universe to implement this interface, but he hoped to do it as much as possible, at least he has abstracted the reference to the boss. Therefore, anyone who implements the IWorkerEvents interface can be notified of the work progress.

However, Peter's boss is still extremely dissatisfied. "Peter !" Boss roar, "why do you want to tell me when to start my work and when I am working? I don't care about these events. You not only forced me to implement these methods, but you also wasted your precious work time waiting for me to return from the event. When my implementation takes a long time, you need to extend your time! Don't you think of other ways to keep bothering me ?"

Therefore, Peter realized that although interfaces are useful in many cases, the granularity of interfaces is not fine enough when processing events. He hopes to only notify the listener of events of real interest. To this end, Peter decided to break down the methods in the interface into several independent delegate functions, each of which seemed to contain a micro interface with only one method:

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;/* 10 points or less */
}
}

Class Universe {
Static void Main (){
Worker peter = new Worker ();
Boss boss = new Boss ();

// Note: we have replaced the Advise method with the value assignment operator.
Peter. completed = new WorkCompleted (boss. WorkCompleted );
Peter. DoWork ();
Console. WriteLine ("Main: worker completed work ");
Console. ReadLine ();
}
}

1.3 static subscribers

With delegation, Peter achieved his goal of not taking the events that the boss does not care about, but Peter still cannot make universe one of his subscribers. Because universe is a fully enclosed entity, it is inappropriate to hook the delegate to instance members (imagine how many resources are required for multiple Universe instances ). On the contrary, Peter needs to hook the delegate to a static member, because the delegate also fully supports the static member:

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

// Note: In the following three lines of code,
// It is not a good habit to use the value assignment operator,
// Read the task to learn how to add the delegate.
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 ();
}
}

2 events

Unfortunately, since universe is too busy and not used to paying attention to a person, universe has managed to replace Peter's boss delegate with its own delegate, this is obviously an unexpected side effect caused by setting the delegate field of the Worker class to public. Similarly, if Peter's boss gets impatient, he can trigger Peter's Commission (Peter's boss is violent)

// Peter's boss controls everything.
If (peter. completed! = Null) peter. completed ();

Peter wants to ensure that these two situations do not happen. He realized that he had to add the registration and anti-registration functions for each delegate, so that the subscriber could add or remove themselves, but no one could empty the entire event list or trigger its events. Peter did not implement these methods himself. Instead, he used the event keyword to let the C # compiler help him build these methods:

Class Worker {
...
Public event WorkStarted started;
Public event WorkProgressing progressing;
Public event WorkCompleted completed;
}

Peter knows the event keyword to make the delegate have this attribute: only allow C # customers to add or remove themselves with the + = or-= operator, which forces the boss and universe to behave elegantly:

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

2.1 get all results

Peter finally breathed a sigh of relief. He has managed to meet the needs of all subscribers and will not be closely coupled with specific implementations. However, he noticed that although the boss and universe both scored points for his work, he only scored one. When there are multiple subscribers, Peter wants to get the score of all subscribers. Therefore, he decided to "Enter the delegate" and extract the subscriber list to manually call them separately:

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

2.2 asynchronous notification: trigger and ignore

Unexpectedly, during this period, the boss and universe were entangled in other things, which meant that their time for scoring Peter's work was greatly extended:

Class Boss {
Public int WorkCompleted (){
System. Threading. Thread. Sleep (3000 );
Console. WriteLine ("Better..."); return 6;/* 10 points or less */
}
}

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

Unfortunately, since Peter notifies every subscriber at the same time and waits for them to score, these notifications that need to return a score now seem to take up a lot of his work time. Therefore, peter decided to ignore the rating and asynchronously trigger the event:

Public void DoWork (){
...
Console. WriteLine ("Worker: work completed ");
If (completed! = Null ){
Foreach (WorkCompleted wc in completed. GetInvocationList ()){
Wc. BeginInvoke (null, null );
}
}
}

2.3 asynchronous notification: Round Robin

This clever trick allows Peter to immediately return to work while notifying subscribers, so that the thread pool of the process calls the delegate. However, it wasn't long before Peter found that the score the subscriber gave him was lost. He knows that he has done a good job and is happy to praise him as a whole (not just his boss. Therefore, Peter triggers events asynchronously, but regularly polls to view the scores that can be obtained:

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

2.4 asynchronous notification: Delegate

Unfortunately, Peter is back to the starting point of the problem, just as he wanted to avoid the boss standing next to him to monitor his work at the beginning. Therefore, Peter decided to use another delegate as the notification method for asynchronous work completion, so that he can immediately go back to work, and when the work is scored, he can still receive the notification:

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

3. Putian tongle

Peter, boss, and universe are all satisfied. Both boss and universe can be notified only of the events they are interested in, reducing the implementation burden and unnecessary back-and-forth calls. Peter can notify each of them, without having to worry about how long it will take to return from those target methods, and still get the scoring results asynchronously. The result shows the complete solution:

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 ){

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

Public event WorkStarted started;
Public event WorkProgressing progressing;
Public event WorkCompleted completed;
}

Class Boss {
Public int WorkCompleted (){
System. Threading. Thread. Sleep (3000 );
Console. WriteLine ("Better..."); return 6;/* 10 points or less */
}
}

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

Static int WorkerCompletedWork (){
System. Threading. Thread. Sleep (4000 );
Console. WriteLine ("Universe is 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 ();
}
}

Peter knows that asynchronous result retrieval may cause some problems. Because an asynchronous event is triggered, the target method may be executed in another thread, Just like Peter's notification of "when the target method is completed. However, Peter is familiar with Chapter 1 "multi-threaded user interfaces", so he knows how to handle such problems when building WinForms applications.

Since then, they have been very happy.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.