(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
[Update: Yes, I know this is broken. There's a bug that means it doesn't work. The complexity in Greg's solution turns out to be necessary... Or at least I've yet to come up with a solution that is simpler and which works.]
Greg Schechter recently wrote a blog on Simulating "Weak Delegates" in the CLR. I thought I'd try a different take on the same theme.
I've written a generic WeakEventHandler
class, which provides a delegate with a weak reference to its
target. It has an implicit conversion to your chosen delegate type, which means it supports the following usage:
EventHandler h = new WeakEventHandler<EventHandler, EventArgs> (p.Handler);
Obviously the fact that I'm using generics here limits this code to VS.NET 2005. However, the exact same technique
I'm using here works just fine without generics if you don't mind writing a version of this class for each delegate type you'd
like to support. The example presented here exploits generics so that it will work with any delegate type that uses
the EventXxxHandler
/EventXxxArgs
style.
The nifty thing about this is that the reference the WeakEventHandler
holds to its target is a
WeakReference
. This means that if the WeakEventHandler
is the only thing still referring
to the target, it won't prevent the target from being garbage collected. For certain styles of event handling (the same
scenarios that Greg's code is intended for) this can be useful.
I think this usage model is somewhat simpler - you do not need to alter either the event source or the event handler.
You can just connect them using a WeakEventHandler
rather than a normal delegate.
Here's the code for the WeakEventHandler
:
public class WeakEventHandler<DT, T> where T : EventArgs { private WeakReference weakRefToOriginalDelegate; public WeakEventHandler(EventHandler<T> originalDelegate) { weakRefToOriginalDelegate = new WeakReference(originalDelegate); } private void DoInvoke(object sender, T args) { EventHandler<T> originalDelegate = (EventHandler<T>) weakRefToOriginalDelegate.Target; if (originalDelegate != null) originalDelegate(sender, args); } public static implicit operator DT(WeakEventHandler<DT, T> wd) { object o = Delegate.CreateDelegate(typeof (DT), wd, "DoInvoke"); return (DT) o; } }
Here's an example showing the delegate in use:
class Program { void Handler(object sender, EventArgs e) { Console.WriteLine("Handler called"); } static void Main(string[] args) { Program p = new Program(); EventHandler h = new WeakEventHandler<EventHandler, EventArgs>(p.Handler); h(null, EventArgs.Empty); p = null; GC.Collect(); Console.WriteLine("GC done. Raising again:"); h(null, EventArgs.Empty); } }
The output of this program is:
Handler called GC done. Raising again:
Note that when we invoke the EventHandler
delegate h
a second time, the
Handler
function doesn't run. This is because we forced a garbage collection. The only reference to the
Program
instance was through the WeakEventHandler
, so the Program
object got collected, and ceases to receive events.
I hit a problem when writing this example. I wanted to apply a constraint to the DT
type parameter to
make sure that a delegate type was always passed in. Unfortunately, when I tried that I got this compiler error:
Cannot convert anonymous block to type 'DT' because it is not a delegate type
This is irritating for two reasons. First, it means that if you try and instantiate the class with a bogus type
parameter, e.g.: WeakEventHandler<string, EventArgs>
, you won't
get a compile-time error. The error will occur at runtime instead, which is unfortunate.
The second reason is that I'm not really happy with that use of Delegate.CreateDelegate
. It works,
but it shouldn't really be necessary - if the delegateness of the DT
type were represented as a constraint,
I should be able to do this:
// Doesn't work! public static implicit operator DT(WeakEventHandler<DT, T> wd) { DT dlg = wd.DoInvoke; return dlg; }
That looks a lot neater to me. That's the code I want to write, and it expresses what the code does. The dynamic creation you have to use in practice is a little less neat, and presumably a little slower since it involves retrieving the function methodinfo by name via reflection. But it's not such a big deal - it's the lack of a compile-time complaint when an inappropriate type parameter is used which annoys me.
The other mildly unsatisfactory thing is that you have to specify both the delegate type and the argument type as type
parameters. It feels like you should be able to infer the one from the other. But I can't see a way around this, except for the
special case of the generic EventHandler<T>
type.