Event bubbling in C#
Published by Carlos Blé on 08/04/2016
How to propagate an event from a low level class to a top level one:
| public class TopLevel{ | |
| public bool Bubbled { get; private set; } | |
| private MiddleLevel observable; | |
| public TopLevel(MiddleLevel observable){ | |
| this.observable = observable; | |
| observable.Triggered += (s, e) => { | |
| Bubbled = true; | |
| }; | |
| } | |
| } | |
| public class MiddleLevel{ | |
| public event EventHandler Triggered; | |
| private BottomLevel observable; | |
| public MiddleLevel(BottomLevel observable){ | |
| this.observable = observable; | |
| //One may be tempted to bubble like this: | |
| //observable.Triggered += Triggered; | |
| //However, Triggered is null unless there is already | |
| //a subscriber. This is a better approach: | |
| observable.Triggered += (s, e) => { | |
| Triggered(s, e); | |
| }; | |
| } | |
| } | |
| public class BottomLevel{ | |
| public event EventHandler Triggered; | |
| public void DoSomething(){ | |
| Triggered(this, EventArgs.Empty); | |
| } | |
| } | |
| [TestFixture] | |
| public class TestingEventBubbling { | |
| [Test] | |
| public void Bubbling(){ | |
| var bottom = new BottomLevel(); | |
| var middle = new MiddleLevel(bottom); | |
| var top = new TopLevel(middle); | |
| bottom.DoSomething(); | |
| top.Bubbled.Should().BeTrue(); | |
| } | |
| } |
Events can only be raised from within the declaring type. Unfortunately they can’t be be passed in as arguments to methods. Only += and -= operators are allowed out of the declaring type. One way to stub out the event could be through inheritance:
| public class BottomLevel{ | |
| public virtual event EventHandler Triggered; | |
| public void DoSomething(){ | |
| Triggered(this, EventArgs.Empty); | |
| } | |
| } | |
| public class StubbedBottomLevel : BottomLevel { | |
| public override event EventHandler Triggered; | |
| public void RaiseEvent(){ | |
| Triggered(this, EventArgs.Empty); | |
| } | |
| } | |
| [TestFixture] | |
| public class TestingEventBubbling { | |
| [Test] | |
| public void BubblingWithStub(){ | |
| var bottom = new StubbedBottomLevel(); | |
| var middle = new MiddleLevel(bottom); | |
| var top = new TopLevel(middle); | |
| bottom.RaiseEvent(); | |
| //bottom.DoSomething(); will not throw the event! | |
| top.Bubbled.Should().BeTrue(); | |
| } | |
| } |
But declaring the event as virtual and then overriding it, is very tricky: replacing the call to RaiseEvent to DoSomething, makes the test fail! Looks like events where not designed to be overridden.
A better approach would be:
| public class BottomLevel{ | |
| public event EventHandler Triggered; | |
| public virtual void DoSomething(){ | |
| //SomeLogic would go here... | |
| Raise(EventArgs.Empty); | |
| } | |
| protected virtual void Raise(EventArgs args){ | |
| Triggered(this, args); | |
| } | |
| } | |
| public class StubbedBottomLevel : BottomLevel { | |
| public override void DoSomething(){ | |
| Raise(EventArgs.Empty); | |
| } | |
| } |
Originally published in Carlos Blé's blog.