I plan to make use of this interface in a plug-in architecture.
/// <summary>
/// Generic interface allowing you to react to an event.
/// You can block the event or just use it for notification.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IEventObserver<in T> where T : class
{
/// <summary>
/// Throw an exception in here if you want to prevent the event firing.
/// </summary>
/// <param name="o"></param>
void CanFireEvent(T o);
/// <summary>
/// This is only called if no handler threw an event in the CanFire.
/// </summary>
/// <param name="o"></param>
void OnBeforeEvent(T o);
/// <summary>
/// This is called after the event has happened.
/// </summary>
/// <param name="o"></param>
void OnAfterEvent(T o);
}
Example usage:
public interface ISendMessageObserver : IEventObserver<SendMessageEventArgs>
{
}
A plug-in can then implement ISendMessageObserver
and prevent message sending by throwing an exception in the CanFireEvent
method. However, I know that exceptions should not be used for normal program flow control, yet it is normal and expected for plugins here to stop the flow. I went for this because I'd like the plugin to provide a reason for blocking the event that can be logged or presented to the user.
I could change CanFireEvent
to:
bool CanFireEvent(T o, ref message);
or:
SomeTypeContainingAbortReason CanFireEvent(T o);
But both of those don't smell ideal either.
So what do people think, exceptions for flow control OK in this instance?
Edit
I've separated the CanFire
into it's own interface and renamed it as PreviewEvent
.
/// <summary>
/// Generic interface allowing you to react to and block an event.
/// </summary>
/// <typeparam name="TArgs"></typeparam>
/// <typeparam name="TBlock"></typeparam>
public interface IEventPreviewer<in TArgs, out TBlock>
where TArgs : class
where TBlock : class, IEventBlock
{
/// <summary>
/// Return an IEventBlock to prevent the event from firing.
/// </summary>
/// <param name="args"></param>
TBlock PreviewEvent(TArgs args);
}
public interface IEventBlock
{
string BlockReason { get; }
}
SomeTypeContainingAbortReason
. I guess that's part of the problem, I don't know whether all reasons for blocking events will be errors because that's up to the plugin implementor. For the most extream example I might write a plugin that blocks all messages, it's not an error and it's not exceptional, that's just's the plugins job.ISendMessageObserver
can have a different return type to otherIEventObserver
s.