I'm working on some code that uses a pattern in its business and data tiers that uses events to signal errors e.g.
resource = AllocateLotsOfMemory();
if (SomeCondition())
{
OnOddError(new OddErrorEventArgs(resource.StatusProperty));
resource.FreeLotsOfMemory();
return;
}
This looked superficially rather odd, especially as the code that calls this needs to hook into the events (there are four or five different ones!).
The developer tells me that this way they can refer to the properties of the allocated resource in the error handling code, and that responsibility for cleaning up after the error is kept by this tier.
Which makes some kind of sense.
The alternative might be something like
resource = AllocateLotsOfMemory();
if (SomeCondition())
{
BigObject temporary = resource.StatusProperty;
resource.FreeLotsOfMemory();
throw new OddException(temporary);
}
My questions are:
As this "BigObject
" is freed when the exception object is released, do we need this pattern?
Has anyone else experience of this pattern? If so, what pitfalls did you find? What advantages are there?
Thanks!
It seems odd to me too. There are a few advantages - such as allowing multiple "handlers" but the semantics are significantly different to normal error handling. In particular, the fact that it doesn't automatically get propagated up the stack concerns me - unless the error handlers themselves throw an exception, the logic is going to keep going as if everything was still okay when it should probably be aborting the current operation.
Another way of thinking about this: suppose the method is meant to return a value, but you've detected an error early. What value do you return? Exceptions communicate the fact that there is no appropriate value to return...
This looks really odd to me, firstly IDisposable is your friend, use it.
If you are dealing with errors and exceptional situations you should be using exceptions, not events, as its much simpler to grasp, debug and code.
So it should be
using(var resource = AllocateLotsOfMemory())
{
if(something_bad_happened)
{
throw new SomeThingBadException();
}
}
If you think in terms of "Errors" and "Warnings", I've had lots of luck when reserving events for the "Warning" category and Exceptions for the "Errors" category.
The rationale here is that events are optional. No one is holding a gun to your head forcing you to handle them. That's probably okay for warnings, but when you have genuine errors you want to make sure they are taken a little more seriously. Exceptions must be handled, or they'll bubble up and create a nasty message for the user.
With regards to your Big Object question: you definitely don't be passing big objects around, but that doesn't mean you can't pass references to big objects around. There's a lot of power in the ability to do that.
As an addendum, there's nothing stopping from from raising an event in addition to the exception, but again: if you have a genuine error you want something to force the client developer to handle it.
Take a look at this post by Udi Dahan. Its an elegant approach for dispatching domain events. The previous poster is correct in saying that you should not be using an event mechanism to recover from fatal errors, but it is a very useful pattern for notification in loosely coupled systems:
public class DomainEventStorage<ActionType>
{
public List<ActionType> Actions
{
get
{
var k = string.Format("Domain.Event.DomainEvent.{0}.{1}",
GetType().Name,
GetType().GetGenericArguments()[0]);
if (Local.Data[k] == null)
Local.Data[k] = new List<ActionType>();
return (List<ActionType>) Local.Data[k];
}
}
public IDisposable Register(ActionType callback)
{
Actions.Add(callback);
return new DomainEventRegistrationRemover(() => Actions.Remove(callback)
);
}
}
public class DomainEvent<T1> : IDomainEvent where T1 : class
{
private readonly DomainEventStorage<Action<T1>> _impl = new DomainEventStorage<Action<T1>>();
internal List<Action<T1>> Actions { get { return _impl.Actions; } }
public IDisposable Register(Action<T1> callback)
{
return _impl.Register(callback);
}
public void Raise(T1 args)
{
foreach (var action in Actions)
{
action.Invoke(args);
}
}
}
And to consume:
var fail = false;
using(var ev = DomainErrors.SomethingHappened.Register(c => fail = true)
{
//Do something with your domain here
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With