10

我正在编写一些代码,这些代码在其业务和数据层中使用一种模式,这些模式使用事件来表示错误,例如

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. 有没有其他人经历过这种模式?如果有,你发现了什么陷阱?有什么优势?

谢谢!

4

9 回答 9

8

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

另一种思考方式:假设该方法旨在返回一个值,但您很早就发现了一个错误。你返回什么价值?异常传达了一个事实,没有适当的值可以返回......

于 2008-09-29T14:29:00.990 回答
4

如果您从“错误”和“警告”的角度来考虑,那么在为“警告”类别保留事件和为“错误”类别保留例外时,我很幸运。

这里的基本原理是事件是可选的。没有人拿着枪指着你的头强迫你去处理它们。这对于警告来说可能没问题,但是当您遇到真正的错误时,您希望确保更加认真地对待它们。必须处理异常,否则它们会冒泡并为用户创建令人讨厌的消息。

关于您的大对象问题:您绝对不会传递大对象,但这并不意味着您不能传递对大对象的引用。这样做的能力有很多。

作为附录,除了异常之外,没有什么可以阻止引发事件,但同样:如果你有一个真正的错误,你想要一些东西来强制客户端开发人员处理它。

于 2008-09-29T14:25:15.600 回答
4

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

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

所以应该是

using(var resource = AllocateLotsOfMemory())
{
   if(something_bad_happened) 
   {
     throw new SomeThingBadException();
   }
}
于 2008-09-29T14:31:27.787 回答
3

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

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

于 2008-09-29T14:28:00.213 回答
3

老实说,指示错误的事件让我觉得很可怕。

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

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

对我来说,如果该方法没有履行它的隐含或显式契约(它没有做它应该做的事情),一个例外是适当的响应。在这种情况下,在异常中抛出您需要的信息似乎是合理的。

于 2008-09-29T14:32:06.317 回答
3

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

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
}
于 2008-09-29T14:49:14.577 回答
1

第一个片段应该是

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

否则,当事件处理程序引发异常时,您将不会释放资源。

正如迈克布朗所说,如果第二个片段resource.FreeLotsOfMemory()与内容混淆resource.StatusProperty而不是将其设置为null.

于 2009-11-17T08:07:02.707 回答
0

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

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

如果 resource.FreeLotsOfMemory() 清除了 StatusProperty 值,而不是仅仅将其设置为 null,那么在创建和抛出 OddException 时,您的临时变量将持有无效对象。

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

于 2008-09-29T14:32:01.450 回答
0

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

对于传统的错误处理,当控制以一种受控的方式向上移动调用堆栈到错误处理程序时,锁将被释放。在这个方案中,当事件被调用时,所有的锁仍然会被持有。错误处理程序中发生的任何阻塞(如果有日志记录,您可能会期望一些阻塞)将是死锁的潜在来源。

于 2008-09-29T15:15:35.933 回答