Daily Archives: December 19, 2013
Events (part 2, advanced)
We are going to construct our custom event accessor now. It deals with additions and removals of subscriptions. Accessors do pretty much look like property definitions. But instead of set and get you have to use add and remove.
public class MyActionEvent4 { private object _Lock = new object(); // a new object simply to avoid lock conflicts private event EventHandler<MyArgs> _OnChange; private event EventHandler<MyArgs> OnChange { add { lock (_Lock) { _OnChange += value; } } remove { lock (_Lock) { _OnChange -= value; } } } // public void RaiseEvent() { lock (_Lock) { EventHandler<MyArgs> lHandler = _OnChange; if (lHandler == null) return; lHandler(this, new MyArgs(0)); } }// } // class
Now we have one big problem here. The RaiseEvent() method has to obtain a lock each time, which causes a serious impact on time sensitive programs. Luckily you do not need to care about changing subscriptions during the invocation. Delegate references are thread-safe, because they are immutable like strings. Let’s simply take the lock out of the RaiseEvent() method, et voilĂ !
public class MyActionEvent5 { private object _Lock = new object(); private event EventHandler<MyArgs> _OnChange; private event EventHandler<MyArgs> OnChange { add { lock (_Lock) { _OnChange += value; } } remove { lock (_Lock) { _OnChange -= value; } } } // public void RaiseEvent() { EventHandler<MyArgs> lHandler = _OnChange; if (lHandler == null) return; lHandler(this, new MyArgs(0)); }// } // class
It is clear that events are not delegates. They restrict access rights from outside of the event class. To describe events you could most likely say that they are wrappers around delegates.
Whenever an exception is thrown during an event call then all following calls will not be executed. And it is tricky to determine which calls were not executed, because the order of event calls is not guaranteed to be in sequence. In fact it does execute in sequence, but there is no guarantee.
static void EventExceptions1() { MyActionEvent3 lEvent = new MyActionEvent3(); lEvent.OnChange += (sender, e) => Console.WriteLine("Executed subscription 1"); lEvent.OnChange += (sender, e) => { throw new Exception("OMG!"); }; lEvent.OnChange += (sender, e) => Console.WriteLine("Executed subscription 3"); lEvent.RaiseEvent(); } //
So you have to deal with exceptions manually if you want to satisfy/execute as many event subscriptions as possible. You could add a try/catch block for each subscription. Or you could invoke the InvocationList yourself by calling the GetInvocationList() method [System.Delegate] and execute each item manually in a try/catch block. Let’s have a look at the following practical solution:
public class MyActionEvent6 { public event EventHandler OnChange = delegate { }; public void RaiseEvent() { List<Exception> lExceptions = new List<Exception>(); foreach (Delegate lHandler in OnChange.GetInvocationList()) { try { lHandler.DynamicInvoke(this, EventArgs.Empty); } catch (Exception ex) { lExceptions.Add(ex); } } if (lExceptions.Count > 0) throw new AggregateException(lExceptions); }// } // class static void EventExceptions6() { MyActionEvent6 lEvent = new MyActionEvent6(); lEvent.OnChange += (sender, e) => Console.WriteLine("Executed subscription 1"); lEvent.OnChange += (sender, e) => { throw new Exception("OMG!"); }; lEvent.OnChange += (sender, e) => Console.WriteLine("Executed subscription 3"); try { lEvent.RaiseEvent(); } catch (AggregateException ex) { foreach (Exception lException in ex.InnerExceptions) { Console.WriteLine(lException.InnerException.Message); } } } //