【问题标题】:When should I use a ThrowHelper method instead of throwing directly?我什么时候应该使用 ThrowHelper 方法而不是直接抛出?
【发布时间】:2010-12-31 02:30:32
【问题描述】:

什么时候适合使用ThrowHelper方法而不是直接抛出?

void MyMethod() {
    ...
    //throw new ArgumentNullException("paramName");
    ThrowArgumentNullException("paramName");
    ...
}
void ThrowArgumentNullException(string paramName) {
    throw new ArgumentNullException(paramName);
}

我读过调用 ThrowHelper 方法(唯一目的是抛出异常的方法)而不是直接抛出 应该 产生更小的字节码。

这一点以及明显的封装(另一层间接)可能是不直接抛出的好理由,至少在某些情况下是这样。

无论如何,IMO 的缺点也不是微不足道的。

  • 异常)控制流的一部分被隐藏了
  • 异常最终具有更神秘的堆栈跟踪
  • 编译器 (2.0) 将无法识别 ThrowHelper 调用是方法的退出点,因此需要一些代码环绕。

我的有限经验是整体设计通常会变得更糟。

int MyMethod(int i) {
    switch (i) {
        case 1:
            return 1;
        default:
            ThrowMyException();
    }
    return 0; // Unreachable (but needed) code
 }

这可能部分是个人喜好问题。无论如何,您对这个问题的个人指导方针是什么?您是否认为将 ThrowHelpers 用于方法参数验证等所有常见任务(ThrowArgumentNullException(paramName) 等)是个好主意? 我在这个问题上遗漏了一些明显的东西吗?

顺便说一句,我试图不将此问题与验证问题混为一谈,例如像这样的方法:

ThrowIfNameIsNullOrEmpty(name);

【问题讨论】:

  • 哇。我以前没有见过这种模式,但和你一样,我的直接反应是非常消极的。对于绝大多数情况,字节码大小肯定是过早的优化。
  • 微软在设计 Stack 时使用了 ThrowHelper:pastebin.com/p2k4URtU

标签: c# exception throw


【解决方案1】:

我的默认方法是直接从异常代码分支中抛出。

DRY 原则和 3 规则指导我何时将其包装在一个方法中:如果我发现自己编写相同的“抛出”代码 3 次或更多次,我会考虑将其包装在一个辅助方法中。

但是,与其抛出一个方法,不如编写一个工厂方法来创建所需的异常,然后从原始位置抛出它:

public void DoStuff(string stuff)
{
    // Do something

    throw this.CreateException("Boo hiss!");
}

private MyException CreateException(string message)
{
    return new MyException(message);
}

这会保留堆栈跟踪。

【讨论】:

    【解决方案2】:

    这对我来说似乎是一个不必要的抽象。你有一个做一件事的方法,它的名字和它所做的事情一样冗长。

    关于更少字节码的论点实际上毫无意义,因为您的代码大小几乎不重要(您不会为每个程序实例节省超过一千字节,除非您从源代码中大量荒谬的地方抛出该异常代码)。同时,您所说的缺点都是真正的问题,尤其是在异常处理的清晰度方面。

    基本上抽象出这样的小事情通常会反过来咬你。 (关于这个主题的一些有趣的阅读:http://www.joelonsoftware.com/articles/LeakyAbstractions.html

    【讨论】:

      【解决方案3】:

      我会说,这样做的唯一合理时间是在像 BCL 这样的类的使用量足够广泛的地方,以至于节省的钱可能是值得的。然而,这必须是有道理的。就您而言,我认为您实际上并没有节省任何空间。 BCL 中ThrowHelper 的实例通过用枚举代替字符串和方法调用(以获取字符串消息)来减小大小。您的案例只是传递了相同的参数,因此没有节省任何费用。调用方法的成本与引发异常的成本一样高。

      【讨论】:

      • 明白你的意思。如果权重不是使用 'throw' 关键字本身而是参数传递,那么这种帮助器不会产生性能提升。
      【解决方案4】:

      据我了解,较小的字节码几乎是唯一的优势 (See this question),所以我不希望您会在这里的答案中看到很多争论。

      【讨论】:

        【解决方案5】:

        我总是尝试抛出我自己的异常来进行定制。我可以抛出一条消息,它会在短时间内告诉我问题是什么(以及问题的根源)。就性能问题而言,我尝试尽可能地限制在我的软件发布版本上抛出的异常数量,所以我实际上从未考虑过太多。不过我有兴趣看看其他人怎么说。

        这是我的 2 美分。请照此办理。

        【讨论】:

          【解决方案6】:

          使用 throw-helper 或 exception-factory 方法尚未提及的一个优点是可以为此目的传递委托。使用 throw-helper 委托时,可以获得不抛出的可能性(这在某些情况下是一件好事;在其他情况下,这是一件坏事)。例如:

          string TryGetEntry(string key, Funct errorHandler) { 如果(处置) 返回错误处理程序(问题原因.ObjectDisposed); else if (...entry_doesnt_exist...) 返回错误处理程序(问题原因.ObjectNotFound); 别的 返回……进入……; } 字符串 GetEntry(字符串键) { 返回 TryGetEntry(key, ThrowExceptionOnError); } bool TryGetEntry(字符串键,参考结果) { 布尔确定; 字符串结果; 结果 = TryGetEntry(key, (problemCause theProblem) => {ok=false; return (string)null;}); 返回确定; }

          使用这种方法,您可以轻松地使用具有多种错误处理策略的例程。可以通过让 TryGetEntry 方法接受泛型类型参数以及该类型的“ref”参数和接受这种“ref”参数的委托来消除对闭包的需要;不过,这个例子更简单,只是使用了一个闭包。

          【讨论】:

          • 这很聪明,但我更喜欢更直接的方法......只是不用担心错误处理程序中的错误...... :-)
          【解决方案7】:

          虽然较小的字节码似乎只能为您节省几 kb,但它肯定会“影响”性能,因为必须获取/加载更多代码才能运行您的方法,这也意味着浪费了更多的 cpu 缓存。如果你编写高性能代码,每个微优化都很重要。

          但更重要的是您可以本地化,或者有一天可能想要本地化您的异常消息。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-01-24
            • 2017-12-04
            • 2016-08-09
            • 2011-12-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多