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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.