There are several problems here (others have already mentioned some of them), but the main thing is that the compiler creates a new delegate object that no one refers to. The compiler accepts
ea.Subscribe<SomeEvent>(SomeHandlerMethod);
and inserts the appropriate delegate transform, effectively providing:
ea.Subscribe<SomeEvent>(new Action<SomeEvent>(SomeHandlerMethod));
Then later this delegate will be collected (there is only your WeakReference for him), and the subscription will be closed.
You also have security issues (I assume you are using ConcurrentDictionary for this purpose). In particular, access to ConcurrentDictionary and List not thread safe at all. Lists must be locked, and you need to use ConcurrentDictionary correctly to create updates. For example, in your current code, it is possible that two separate threads are in the TryAdd block, and one of them will not end, which will lead to the loss of the subscription.
We can fix these problems, but let me outline a solution. A weak event schema can be difficult to implement in .Net because of these automatically generated delegate instances. Instead, the Target delegate will be captured in WeakReference if it has one (this could be if it is a static method). Then, if the method is an instance method, we will build the equivalent of Delegate , which has no purpose, and therefore there will be no strong reference.
using System.Collections.Concurrent; using System.Diagnostics; public class EventAggregator { private readonly ConcurrentDictionary<Type, List<Subscriber>> subscribers = new ConcurrentDictionary<Type, List<Subscriber>>(); public void Subscribe<TMessage>(Action<TMessage> handler) { if (handler == null) throw new ArgumentNullException("handler"); var messageType = typeof(TMessage); var handlers = this.subscribers.GetOrAdd(messageType, key => new List<Subscriber>()); lock(handlers) { handlers.Add(new Subscriber(handler)); } } public void Publish(object message) { if (message == null) throw new ArgumentNullException("message"); var messageType = message.GetType(); List<Subscriber> handlers; if (this.subscribers.TryGetValue(messageType, out handlers)) { Subscriber[] tmpHandlers; lock(handlers) { tmpHandlers = handlers.ToArray(); } foreach (var handler in tmpHandlers) { if (!handler.Invoke(message)) { lock(handlers) { handlers.Remove(handler); } } } } } private class Subscriber { private readonly WeakReference reference; private readonly Delegate method; public Subscriber(Delegate subscriber) { var target = subscriber.Target; if (target != null) {
And the test program:
public class Program { public static void Main(string[] args) { var agg = new EventAggregator(); var test = new Test(); agg.Subscribe<Message>(test.Handler); agg.Subscribe<Message>(StaticHandler); agg.Publish(new Message() { Data = "Start test" }); GC.KeepAlive(test); for(int i = 0; i < 10; i++) { byte[] b = new byte[1000000];
mike z
source share