荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: michaelx (好好学习), 信区: DotNET
标  题: .NET Delegates: A C# Bedtime Story
发信站: 荔园晨风BBS站 (Sun Dec  1 12:05:24 2002), 站内信件

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

And they all lived happily every after. The end.

--

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 61.144.235.39]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店