0% found this document useful (0 votes)
27 views11 pages

The Following Is An Excerpt From, Chris Sells & Michael Weinhardt, Addison-Wesley, 2006. It's Been Updated From The Original Version For C# 2.0

1) The document describes how a worker named Peter uses delegates in C# to notify interested parties like his boss and the universe about the progress of his work, without coupling them directly. 2) Initially, Peter implemented this by calling methods on his boss directly, but then abstracted it using an interface to allow other listeners. However, interfaces were not granular enough. 3) Peter then used delegates to allow interested parties to subscribe to specific events, like work starting or completing. This avoided notifying parties who did not want certain events. Events allowed safe addition and removal of listeners.

Uploaded by

gpezzella
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
27 views11 pages

The Following Is An Excerpt From, Chris Sells & Michael Weinhardt, Addison-Wesley, 2006. It's Been Updated From The Original Version For C# 2.0

1) The document describes how a worker named Peter uses delegates in C# to notify interested parties like his boss and the universe about the progress of his work, without coupling them directly. 2) Initially, Peter implemented this by calling methods on his boss directly, but then abstracted it using an interface to allow other listeners. However, interfaces were not granular enough. 3) Peter then used delegates to allow interested parties to subscribe to specific events, like work starting or completing. This avoided notifying parties who did not want certain events. Events allowed safe addition and removal of listeners.

Uploaded by

gpezzella
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 11

.

NET Delegates: A C# Bedtime Story Pagina 1 di 11

.NET Delegates: A C# Bedtime Story


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");
if( this.boss != null ) this.boss.WorkStarted();

Console.WriteLine("Worker: work progressing");


if( this.boss != null ) this.boss.WorkProgressing();

Console.WriteLine("Worker: work completed");


if( this.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();

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014
.NET Delegates: A C# Bedtime Story Pagina 2 di 11

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");
if( this.events != null ) this.events.WorkStarted();

Console.WriteLine("Worker: work progressing");


if( this.events != null ) this.events.WorkProgressing();

Console.WriteLine("Worker: work completed");


if( this.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

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014
.NET Delegates: A C# Bedtime Story Pagina 3 di 11

}
}

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");
if( this.Started != null ) this.Started();

Console.WriteLine("Worker: work progressing");


if( this.Progressing != null ) this.Progressing();

Console.WriteLine("Worker: work completed");


if( this.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 {

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014
.NET Delegates: A C# Bedtime Story Pagina 4 di 11

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

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014
.NET Delegates: A C# Bedtime Story Pagina 5 di 11

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

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014
.NET Delegates: A C# Bedtime Story Pagina 6 di 11

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

if( this.Completed != null ) {


foreach( WorkCompleted wc in this.Completed.GetInvocationList
() ) {
int grade = wc();
Console.WriteLine("Worker grade= {0}", 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");

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014
.NET Delegates: A C# Bedtime Story Pagina 7 di 11

if( this.Completed != null ) {


foreach( WorkCompleted wc in this.Completed.GetInvocationList
() ) {
wc.BeginInvoke(null, null); // 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");
if( this.Completed != null ) {
foreach( WorkCompleted wc in this.Completed.GetInvocationList
() ) {
IAsyncResult result = wc.BeginInvoke(null, null);
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");
if( this.Completed != null ) {
foreach( WorkCompleted wc in this.Completed.GetInvocationList
() ) {
wc.BeginInvoke(this.WorkGraded, wc);

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014
.NET Delegates: A C# Bedtime Story Pagina 8 di 11

}
}
}

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");
if( this.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");

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014
.NET Delegates: A C# Bedtime Story Pagina 9 di 11

if( this.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");
if( this.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);
}
}
}
}

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:

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014
.NET Delegates: A C# Bedtime Story Pagina 10 di 11

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");
if( this.Started != null )
this.Started();

Console.WriteLine("Worker: work progressing");


if( this.Progressing != null )
this.Progressing();

Console.WriteLine("Worker: work completed");


if( this.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() {

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014
.NET Delegates: A C# Bedtime Story Pagina 11 di 11

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.

http://www.sellsbrothers.com/writing/delegates.htm 30/03/2014

You might also like