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:
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);
}
}