使用事件而不是异常来实现错误处理

时间:2008-09-29 14:22:27

标签: c# design-patterns exception events

我正在研究一些在其业务和数据层中使用模式的代码,这些代码使用事件来指示错误,例如:

resource = AllocateLotsOfMemory();
if (SomeCondition())
{    
    OnOddError(new OddErrorEventArgs(resource.StatusProperty));
    resource.FreeLotsOfMemory();    
    return;
}

这看起来很奇怪,特别是当调用它的代码需要挂钩事件时(有四个或五个不同的!)。

开发人员告诉我,这样他们就可以在错误处理代码中引用已分配资源的属性,并且在此层保留错误后清理的责任。

这有点儿意义。

替代方案可能类似

resource = AllocateLotsOfMemory();
if (SomeCondition())
{   
    BigObject temporary = resource.StatusProperty;
    resource.FreeLotsOfMemory();
    throw new OddException(temporary);
}

我的问题是:

  1. 当释放异常对象时释放“BigObject”,我们是否需要这种模式?

  2. 有没有其他人体验过这种模式?如果是这样,你发现了什么陷阱?有什么优势?

  3. 谢谢!

9 个答案:

答案 0 :(得分:8)

我也觉得很奇怪。有一些优点 - 例如允许多个“处理程序”,但语义与正常的错误处理明显不同。特别是,它不会自动传播到栈中这一事实让我感到担忧 - 除非错误处理程序本身抛出异常,否则逻辑将继续进行,就好像它应该可以中止当前操作一样

另一种思考方式:假设该方法是为了返回一个值,但是你早就发现了一个错误。你有什么价值?例外情况表明 没有适当的值返回......

答案 1 :(得分:4)

如果您考虑“错误”和“警告”,我在为“警告”类别保留事件和“错误”类别的例外时有很多运气。

这里的理由是事件是可选的。没有人拿枪对着你的头强迫你处理它们。这可能对警告没问题,但是如果你有真正的错误,你想确保他们更加认真。例外必须处理,否则它们会冒泡并为用户创建令人讨厌的消息。

关于你的大对象问题:你肯定不会传递大对象,但这并不意味着你不能将引用传递给大周围的物体。能够做到这一点有很多力量。

作为附录,没有什么可以阻止将事件添加到异常中,但是再次:如果你有一个真正的错误,你想要某些东西迫使客户开发者处理它。 / p>

答案 2 :(得分:4)

这看起来很奇怪,首先IDisposable是你的朋友,使用它。

如果您正在处理错误和异常情况,您应该使用异常,而不是事件,因为它更容易掌握,调试和编码。

所以它应该是

using(var resource = AllocateLotsOfMemory())
{
   if(something_bad_happened) 
   {
     throw new SomeThingBadException();
   }
}

答案 3 :(得分:3)

1)需要吗?没有模式是绝对必要的

2)Windows Workflow Foundation使用托管运行时内运行的工作流实例的所有结果执行此操作。请记住,在尝试引发该事件时可能会发生异常,并且您可能希望根据情况在Dispose或finally块上执行清理代码以确保它运行。

答案 4 :(得分:3)

说实话,事件信号错误让我感到恐惧。

关于返回状态代码和抛出异常,难民营之间存在分歧。为了简化(大大):状态代码阵营表示抛出异常会将错误检测和处理错误放置在导致错误的代码之外。异常抛出上限表示用户忘记检查状态代码,异常强制执行错误处理。

错误,因为事件似乎是两种方法的最差。错误清理与导致错误的代码完全分开,错误通知完全是自愿的。哎哟。

对我而言,如果方法没有实现它的隐式或显式契约(它没有做它应该做的事情),一个例外就是适当的反应。在这种情况下,抛出异常所需的信息似乎是合理的。

答案 5 :(得分:3)

看看Udi Dahan的this post。它是调度域事件的优雅方法。前面的海报是正确的说你不应该使用事件机制来从致命错误中恢复,但它是一个非常有用的模式,用于松散耦合系统中的通知:

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

消费:

var fail = false;
using(var ev = DomainErrors.SomethingHappened.Register(c => fail = true) 
{
   //Do something with your domain here
}

答案 6 :(得分:1)

第一个片段应该是

resource = AllocateLotsOfMemory();
if (SomeCondition())
{
    try
    {
        OnOddError(new OddErrorEventArgs(resource.StatusProperty));
        return;
    }
    finally
    {
        resource.FreeLotsOfMemory();
    }
}

否则,当事件处理程序抛出异常时,您将无法释放资源。

正如迈克·布朗所说,如果resource.FreeLotsOfMemory()混淆了resource.StatusProperty的内容,而不是将其设置为null,则第二个代码段也会出现问题。

答案 7 :(得分:0)

我们有一个基本的Error对象和ErrorEvent,我们在框架中使用命令模式来处理非关键错误(例如验证错误)。与异常一样,人们可以监听基本的ErrorEvent或更具体的ErrorEvent。

你的两个片段之间也存在显着差异。

如果resource.FreeLotsOfMemory()清除StatusProperty值而不是仅将其设置为null,则在创建并抛出OddException时,临时变量将保持无效对象。

经验法则是,只应在不可恢复的情况下抛出异常。我真的希望C#支持一个Throws子句,这是我唯一真正想念的Java。

答案 8 :(得分:0)

这种方法的另一个主要问题是并发问题。

使用传统的错误处理,当控件以受控方式向上移动调用堆栈到错误处理程序时,将释放锁。在此方案中,调用事件时仍将保留所有锁定。错误处理程序中发生的任何阻塞(如果存在日志记录,您可能会发现某些阻塞)将是潜在的死锁源。