I am toying with an event aggregator using a weak reference to the method in my subscriber object I wish to handle the event.
When subscribing the weak reference is created successfully and my subscribers collection is updating accordingly. When I attempt to publish an event however, the weak reference has been cleaned up by the GC. Below is my code:
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);
if (this.subscribers.ContainsKey(messageType))
{
this.subscribers[messageType].Add(new Subscriber(handler));
}
else
{
this.subscribers.TryAdd(messageType, new List<Subscriber> {new Subscriber(handler)});
}
}
public void Publish(object message)
{
if (message == null)
{
throw new ArgumentNullException("message");
}
var messageType = message.GetType();
if (!this.subscribers.ContainsKey(messageType))
{
return;
}
var handlers = this.subscribers[messageType];
foreach (var handler in handlers)
{
if (!handler.IsAlive)
{
continue;
}
var actionType = handler.GetType();
var invoke = actionType.GetMethod("Invoke", new[] {messageType});
invoke.Invoke(handler, new[] {message});
}
}
private class Subscriber
{
private readonly WeakReference reference;
public Subscriber(object subscriber)
{
this.reference = new WeakReference(subscriber);
}
public bool IsAlive
{
get
{
return this.reference.IsAlive;
}
}
}
}
I subscribe and publish via:
ea.Subscribe<SomeEvent>(SomeHandlerMethod);
ea.Publish(new SomeEvent { ... });
I am probably doing something very daft, that said I am struggling to see my error.
There are a few issues here (others have mentioned some of them already), but the primary one is that the compiler is creating a new delegate object that no one is holding a strong reference to. The compiler takes
ea.Subscribe<SomeEvent>(SomeHandlerMethod);
and inserts the appropriate delegate conversion, giving effectively:
ea.Subscribe<SomeEvent>(new Action<SomeEvent>(SomeHandlerMethod));
Then later this delegate is collected (there is only your WeakReference to it) and the subscription is hosed.
You also have thread-safety issues (I'm assuming you are using ConcurrentDictionary for this purpose). Specifically the access to both the ConcurrentDictionary and the Lists are not thread-safe at all. The Lists need to be locked and you need to properly use ConcurrentDictionary to make updates. For example, in your current code, it is possible that two separate threads are in the TryAdd block and one of them will fail causing a subscription to be lost.
We can fix these problems, but let me outline the solution. The weak event pattern can be tricky to implement in .Net because of those automatically generated delegate instances. What will do instead is capture the delegate's Target in a WeakReference, if it has one (It may not if it is a static method). Then if the method is an instance method we will construct an equivalent Delegate that has no Target and thus 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)
{
// An instance method. Capture the target in a WeakReference.
// Construct a new delegate that does not have a target;
this.reference = new WeakReference(target);
var messageType = subscriber.Method.GetParameters()[0].ParameterType;
var delegateType = typeof(Action<,>).MakeGenericType(target.GetType(), messageType);
this.method = Delegate.CreateDelegate(delegateType, subscriber.Method);
}
else
{
// It is a static method, so there is no associated target.
// Hold a strong reference to the delegate.
this.reference = null;
this.method = subscriber;
}
Debug.Assert(this.method.Target == null, "The delegate has a strong reference to the target.");
}
public bool IsAlive
{
get
{
// If the reference is null it was a Static method
// and therefore is always "Alive".
if (this.reference == null)
return true;
return this.reference.IsAlive;
}
}
public bool Invoke(object message)
{
object target = null;
if (reference != null)
target = reference.Target;
if (!IsAlive)
return false;
if (target != null)
{
this.method.DynamicInvoke(target, message);
}
else
{
this.method.DynamicInvoke(message);
}
return true;
}
}
}
And a 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]; // allocate some memory
agg.Publish(new Message() { Data = i.ToString() });
Console.WriteLine(GC.CollectionCount(2));
GC.KeepAlive(b); // force the allocator to allocate b (if not in Debug).
}
GC.Collect();
agg.Publish(new Message() { Data = "End test" });
}
private static void StaticHandler(Message m)
{
Console.WriteLine("Static Handler: {0}", m.Data);
}
}
public class Test
{
public void Handler(Message m)
{
Console.WriteLine("Instance Handler: {0}", m.Data);
}
}
public class Message
{
public string Data { get; set; }
}
The delegate object that wraps your SomeHandlerMethod behind the scenes is probably garbage collected between Subscribe and Publish.
Try the following:
Action<SomeEvent> action = SomeHandlerMethod;
ea.Subscribe<SomeEvent>(SomeHandlerMethod);
ea.Publish(new SomeEvent { ... });
GC.KeepAlive(action);
Perhaps the old syntax is a bit clearer in this case:
Action<SomeEvent> action = new Action<SomeEvent>(SomeHandlerMethod);
Another thing to watch out for if your code is multithreaded is the race condition where a subscribed event might not be added (TryAdd can return false).
As for a solution, see atomaras answer:
public void Subscribe<TMessage>(IHandle<TMessage> handler)
{
[...]
public interface IHandler<T>
{
Handle(T event);
}
Or:
public void Subscribe<TMessage>(Action<TMessage> handler)
{
[...]
object targetObject = handler.Target;
MethodInfo method = handler.Method;
new Subscriber(targetObject, method)
[...]
subscriber.method.Invoke(subscriber.object, new object[]{message});
I don't know if the reflection MethodInfo object could be stored in a WeakReference, i.e. if it is temporary or not, and if it's stored strongly referenced whether or not it'll hold on to the assembly containing the Type (if we're talking about a dll-plugin)...
You are passing in an instance of an Action that noone keeps a strong reference to so it's immediatelly available for Garbage Collection. Your action does hold a strong reference to your instance with the method though (if it's not static).
What you can do if you want to maintain the same API signature (you have the option of passing in an IHandle interface also if you want) is change the Subscribe parameter to be an Expression, parse it and locate the instance of the Target object of the Action and keep a WeakReference to that instead.
See here on how to do it Action delegate. How to get the instance that call the method
Related
I'm using the code from Weak Events in .Net, the easy way to handle monitoring changes to an observable collection. The code has worked without any problems for months. I recently updated to a new computer. After getting everything setup and pulling down the code from my repository I encountered a strange problem. The code no longer works!
Here's the relevant portion of my code, it all takes place in the constructor:
public class PurchaseOrderReceipt : BaseEntity
{
/// <summary>
/// Initializes a new instance of the <see cref="PurchaseOrderReceipt" /> class.
/// </summary>
public PurchaseOrderReceipt()
{
this.ReceiptItems = new ObservableCollection<PurchaseOrderItemReceipt>();
this.DateReceived = DateTime.Now;
this.ReceiptItems.ObserveCollectionChanged()
.SubscribeWeakly(this, (target, eventArgs) => target.ReceiptItemsChanged());
}
The exception is thrown on the SubscribeWeakly line with the following error message: ArgumentException: onNext must refer to a static method, or else the subscription will still hold a strong reference to target
I can recreate the problem in LinqPad just by creating an instance of the PurchaseOrderReceipt.
Odder still if I write a simple class in LinqPad that mirrors the setup in the PurchaseOrderReceipt class than it works.
LinqPad code:
void Main()
{
var x = new Test();
x.ReceiptItems.Add(new PurchaseOrderItemReceipt());
}
public class Test:BaseEntity
{
public ObservableCollection<PurchaseOrderItemReceipt> ReceiptItems {get; set;}
public Test()
{
this.ReceiptItems = new ObservableCollection<PurchaseOrderItemReceipt>();
this.ReceiptItems.ObserveCollectionChanged().SubscribeWeakly(this,(target, eventargs) => target.TestChanged());
}
private void TestChanged()
{
"Changed!".Dump();
}
}
Changed! is printed out in the results window.
Here's the CustomReactiveExtension class from the link at the top.
public static class CustomReactiveExtension
{
public static IObservable<EventPattern<NotifyCollectionChangedEventArgs>> ObserveCollectionChanged(this INotifyCollectionChanged collection)
{
return Observable.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
handler => (sender, e) => handler(sender, e),
handler => collection.CollectionChanged += handler,
handler => collection.CollectionChanged -= handler);
}
public static IDisposable SubscribeWeakly<T, TTarget>(this IObservable<T> observable, TTarget target, Action<TTarget, T> onNext) where TTarget : class
{
var reference = new WeakReference(target);
if (onNext.Target != null)
{
throw new ArgumentException("onNext must refer to a static method, or else the subscription will still hold a strong reference to target");
}
IDisposable subscription = null;
subscription = observable.Subscribe(item =>
{
var currentTarget = reference.Target as TTarget;
if (currentTarget != null)
{
onNext(currentTarget, item);
}
else
{
subscription.Dispose();
}
});
return subscription;
}
}
Any ideas?
I am not 100% certain but my guess is that either different versions of the compiler or different compilation options are resulting in your lambda being compiled to an instance method rather than a static method.
The easiest solution to this would be to explicitly implement a static method to be used as your onNext callback, i.e:
private static void OnReceiptItemsChanged(PurchaseOrderReceipt target,
EventPattern<NotifyCollectionChangedEventArgs> eventPattern)
{
// TODO Do something here
}
And then use SubscribeWeakly like so:
this.ReceiptItems.ObserveCollectionChanged().SubscribeWeakly(this, OnReceiptItemsChanged);
Now regardless of which compiler you use or which compilation options the callback is always a static method.
I use a library[1] that defines some stuff I would like to make use of:
public delegate void FooHandler(int i);
public delegate void BarHandler(string s);
public class Foo
{
public event FooHandler Fooh;
}
public class Bar
{
public event BarHandler Barh;
}
I would like to have a short term memory, that I can "attach" to these events:
public class ShortTermMemory<T>
{
public ShortTermMemory(??? arg)
{
arg += t => Remeber = t;
}
public T Remember { get; private set; }
}
and attach them somewhere like
var foo = new Foo();
var bar = new Bar();
var intmemory = new ShortTermMemory<int>(foo.Fooh);
var stringmemory = new ShortTermMemory<string>(bar.Barh);
This is impossible because:
??? is (somewhat understandably) not a valid type declaration, and
"The event can only be used on the left hand side of += or -="
Is there anything I can do to fix this, or is this fundamentally impossible in C#? I would like to have compile time guarantees that ShortTermMemory is only "fed" by one event source, which is known at compile time to have a single argument of type T.
[1]: Example implementation for demonstration purposes only
With the compile time guarantees you are expecting it isn't possible in C#. There are a few problems there:
events are backed by a private delegate field which has a combined invocation list. This is considered an implementation detail, thus the private access.
We need to reference the event's delegate field to apply add and remove (+=/-=) operators with Delegate.Combine/Remove.
As C# handles delegates as own types, we should have a generic definition of both FooHandler and BarHandler.
You're missing an unsubscription from the event, possibly placed in the destructor of ShortTermMemory`1. At this time, the event object may not exist anymore, see How to save a ref variable for later use?
Working solution:
public delegate void Handler<T> ( T i );
public class Foo
{
public event Handler<int> Fooh;
public void Set(int i) {
if (Fooh != null) {
Fooh(i);
}
}
}
public class ShortTermMemory<T>
{
object obj = null;
string eventName;
Handler<T> handler;
public ShortTermMemory (object obj, string eventName) {
this.obj = obj;
this.eventName = eventName;
this.handler = new Handler<T>(Set);
Type type = obj.GetType();
EventInfo info = type.GetEvent(eventName);
info.AddEventHandler(obj, handler);
}
~ShortTermMemory() {
if (obj != null) {
Type type = obj.GetType();
EventInfo info = type.GetEvent(eventName);
info.RemoveEventHandler(obj, handler);
}
}
public T Remember { get; set; }
public void Set(T t) {
Remember = t;
}
}
var foo = new Foo();
var intmemory1 = new ShortTermMemory<int>(foo, "Fooh");
var intmemory2 = new ShortTermMemory<int>(foo, "Fooh");
foo.Set(10);
I have an interface: IRemoteDataChangedListener
public interface IRemoteDataChangedListener<TData>
{
void DataReceived(TData newData);
}
And a class, RealtimeEventService
public class RealtimeEventService : IRealtimeEventService
{
private readonly IEventListener listener;
private readonly List<Tuple<Type, WeakReference>> dataCreated;
public RealtimeEventService(IEventListener eventListener)
{
this.dataCreated = new List<Tuple<Type, WeakReference>>();
this.listener = eventListener;
this.listener.EventReceived += this.ListenerOnEventReceived;
}
private void ListenerOnEventReceived(EventMessage message)
{
Type type = message.GetType();
if (type == typeof(NotificationReadEventMessage))
{
this.DataChanged((NotificationReadEventMessage)message);
}
}
public void SubscribeDataChanged<TEventMessage>(IRemoteDataChangedListener<TEventMessage> dataChangedListener) where TEventMessage : EventMessage, new()
{
this.dataCreated.Add(Tuple.Create(typeof(TEventMessage), new WeakReference(dataChangedListener)));
}
internal void DataChanged<TKey>(TKey newData)
where TKey : class, new()
{
LoopAndFilter<TKey>(this.dataCreated, listener => listener.DataReceived(newData));
}
private static void LoopAndFilter<TKey>(ICollection<Tuple<Type, WeakReference>> collection,
Action<IRemoteDataChangedListener<TKey>> success) where TKey : class
{
foreach (var reference in collection.ToArray())
{
if (!reference.Item2.IsAlive)
{
collection.Remove(reference);
continue;
}
if (reference.Item1 != typeof(TKey))
continue;
success((IRemoteDataChangedListener<TKey>)reference.Item2.Target);
}
}
#endregion
}
Whenever I create a test class that inherits IRemoteDataChangedListener with NotificationReadEventMessage as generic argument, and use an instance of this class with SubscribeDataChanged(), it gets hooked up just fine, and the method gets called.
Problem is, when I set the instance reference to null and run GC.Collect(), it should then be null, and the next time RealtimeEventService's LoopAndFilter method runs, it should detect that it is no longer alive, and remove the Weakreference from the list.
However it does not. When I inspect the value (In LoopAndFilter), after setting the instance reference to null in the test, the value still shows up as Alive being true.
And now I've been staring at this code for hours, and I simply cannot find anywhere I'd have a strong reference to the class...
Any help?
#Edit: Unit test (Using the Moq and Should libraries):
public class RealtimeEventServiceTests
{
[Fact]
public void VerifyWeakReferencesWorksAsIntended()
{
var eventListenerMock = new Mock<IEventListener>();
IRealtimeEventService service = new RealtimeEventService(eventListenerMock.Object);
bool called = false;
RemoteDataTest dataChangedListener = new RemoteDataTest();
dataChangedListener.Called += (sender, args) => called = true;
service.SubscribeDataChanged(dataChangedListener);
called.ShouldBeFalse();
((RealtimeEventService)service).DataChanged(new NotificationReadEventMessage());
called.ShouldBeTrue();
called = false;
dataChangedListener = null;
GC.Collect();
called.ShouldBeFalse();
((RealtimeEventService)service).DataChanged(new NotificationReadEventMessage());
called.ShouldBeFalse();
}
}
public class RemoteDataTest : IRemoteDataChangedListener<NotificationReadEventMessage>
{
public event EventHandler Called;
public void DataReceived(NotificationReadEventMessage newData)
{
if (Called != null) Called(this, null);
}
}
As it turns out, when I got home and compiled it at home, it ran just fine. And when I got back to work, it worked fine there as well.
Guess it's just one of those spooky bugs that magically vanish at inexplicable times. I'm just glad to be rid of it.
I did take the advice of Ewan & Scott Chamberlain, so thanks for that!
I want to create a dictionary like this:
public class MyClass
{
public delegate void CreateClickEvent(string value);
public event CreateClickEvent OnCreateClick;
public delegate void CreateHoverEvent(string value);
public event CreateHoverEvent OnCreateHover;
private Dictionary<string,Delegate> _myDictionary = null;
public MyClass()
{
_myDictionary = new Dictionary<string,Delegate>();
_myDictionary.Add("Click", OnCreateClick);
_myDictionary.Add("Hover", OnCreateHover);
}
public void Call(string name)
{
Delegate action;
if (_myDictionary.TryGetValue(name, out action))
{
if (action != null)
action.DynamicInvoke( "something" );
else
Console.WriteLine("null");
}
else
Console.WriteLine("Couldn't find action");
}
}
public class Tester()
{
public Tester()
{
MyClass MyClass = new MyClass();
myClass.OnCreateClick += RaiseCreateClickEvent;
myClass.OnCreateHover += RaiseCreateHoverEvent;
}
public void RaiseCreateClickEvent(string value)
{
///do it here
}
public void RaiseCreateHoverEvent(string value)
{
///do it here
}
}
but unfortunately, the method RaiseCreateClickEvent or RaiseCreateHoverEvent (class Tester) is not calling from Call method (class MyClass). And it's not giving any error and action variable is looking null and printing null.
What am I doing wrong here?
You have dictionary of delegates, but it keeps values of your events at the moment of creation of the dictionary. At that point both OnCreateXXXX are null.
As an option can use dictionary of Action and call event in each so action will use current value of the event, not the initial one:
private Dictionary<string,Action<string>> _myDictionary =
new Dictionary<string,Action<string>>();
_myDictionary.Add("Click", s => OnCreateClick(s));
_myDictionary.Add("Hover", s => OnCreateHover(s));
And use:
_myDictionary["Click"] ("sometext");
Note good reading on delegates (and especially how + / += impacts value) is available http://csharpindepth.com/Articles/Chapter2/Events.aspx
What is the syntax to return an event from a function? (Not to call the event, to return it so that it can be bound to functions).
I have a container class that contains a dictionary where each members has an event.
The aim is to be able to write something like this:
Container c = new Container();
c.CreateEventForKey("a"); // Create the member in the dictionary
c.EventForKey("a") += some_function; // Bind some_function to the event in the "a" member
c.OnEventForKey("a","b"); // Calls some_function with argument "b"
The Container class looks like this:
public class Container {
public class Member {
public event Action<string> AnEvent;
public void OnEvent( string v ) { if(AnEvent!=null) { AnEvent(v); } }
}
protected Dictionary<string,Member> members;
// This seems to work OK.
public void OnEventForKey(string k, string v) {
if ( members.ContainsKey(k) ) { members[k].OnEvent(v); }
else { /* report error */ }
}
// Can't get this to compile.
public event Action<string> EventForKey(string k ) {
if ( members.ContainsKey(k) ) { return members[k].AnEvent; }
else { /* report error */ }
}
}
How can I define EventForKey so that this does what I expect?
What is the syntax to return an event from a function?
You can't, easily. Events - like properties - aren't really first class "objects" as such; they're members of a class. You don't really have a class member here - you're trying to just keep delegates in a dictionary.
You could create your own "event-like" container, but it's probably better to consider alternative designs, e.g.
c.Subscribe("a", SomeFunction);
c.OnEventForKey("a");
You might want to look at EventHandlerList for inspiration.
Why not simply return member and subscribe to it's event?
public IMember MemberForKey(string key) // return IMember
{
if (!members.ContainsKey(key))
throw new Exception();
return members[key];
}
And then subscribe:
Container c = new Container();
c.CreateEventForKey("a");
c.MemberForKey("a").AnEvent += some_function;
c.OnEventForKey("a", "b");
But you have public OnEvent method in Member class. In order to forbid raising events by client, you can create interface which will show only event. Just implement this interface by Member class:
public interface IMember
{
event Action<string> AnEvent;
}
And yes, you cannot return event, because actually event is not object, it is set of two methods add and remove, which add and remove delegates to inner field of delegate type. Here is how your event looks like:
private Action<string> _action; // field of delegate type
public event Action<string> AnEvent
{
add { _action += value; }
remove { _action -= value; }
}
Purpose of event is to provide only two operations for clients - adding and removing handlers. Delegate itself is hidden to clients. You can make it public:
public Action<string> _action;
But in this case any client can invoke it.
UPDATE: if you want to go with Subscribe/Remove syntax, then just use dictionary with handlers:
public class Container
{
private Dictionary<string, Action<string>> handlers =
new Dictionary<string, Action<string>>();
public void CreateEventForKey(string key)
{
// with empty handler added you can avoid null check
handlers.Add(key, (value) => { });
}
public void OnEventForKey(string key, string value)
{
if (!handlers.ContainsKey(key))
throw new Exception();
handlers[key](value);
}
public void Subscribe(string key, Action<string> handler)
{
if (!handlers.ContainsKey(key))
throw new Exception();
handlers[key] += handler;
}
}
Here's complete working example:
class Program
{
static void Main(string[] args)
{
Container c = new Container();
c.CreateEventForKey("a"); // Create the member in the dictionary
c.EventForKey("a").Add(str => Console.WriteLine(str));
c.EventForKey("a").Add(str => Console.WriteLine(str.ToUpper()));
c.OnEventForKey("a", "baa baa black sheep");
Console.ReadLine();
}
}
public class Container
{
public class Member
{
public List<Action<string>> AnEvent = new List<Action<string>>();
public void OnEvent(string v)
{
if (AnEvent != null)
{
this.AnEvent.ForEach(action => action(v));
}
}
public void AddEvent(Action<string> action)
{
this.AnEvent.Add(action);
}
}
protected Dictionary<string, Member> members = new Dictionary<string,Member>();
public void CreateEventForKey(string key)
{
this.members[key] = new Member();
}
// This seems to work OK.
public void OnEventForKey(string k, string v)
{
if (members.ContainsKey(k)) { members[k].OnEvent(v); }
else { /* report error */ }
}
public List<Action<string>> EventForKey(string k)
{
if (members.ContainsKey(k)) { return members[k].AnEvent; }
else { throw new KeyNotFoundException(); }
}
}
The difference is to behave similarly to an event by using a list of delegates.