.NET返回值与抛出异常设计问题

时间:2011-02-03 09:50:02

标签: c# .net exception-handling return-value

假设我们在处理这种结构的类中的树分层数据结构中有一个处理操作的方法。

让我们仔细研究其中一种方法:

void MoveNode(Node currentNode, Node newParentNode)
{
    /* check if new parent isn't current's child already */
     if (newParentNode.LMargin < currentNode.LMargin && newParentNode.RMargin > currentNode.RMargin)
     {
        //DO WORK
     }
     else throw new ArgumentException("New parent node cannot be current's own child");
}

MSDN声明:不要抛出异常来控制流量!

我的问题:您认为这种ArgumentException的使用是否正确,或者您是否会使用某种返回值。 如果是这样,您将如何提供错误/验证消息。

4 个答案:

答案 0 :(得分:10)

由于抛出的异常表示此处存在错误,因此它不会被抛入正常工作的程序中,异常是正确的选择。

你不应该做的是:

try
{
   MoveNode(...)
   //Do something
}
catch(ArgumentException e)
{
  //Do something else
}

在该示例中,您希望定期抛出异常并使用它来控制控制流。在呼叫者中捕获ArgumentException几乎总是一个坏主意。这种异常只应该在顶级处理程序中捕获,如果有的话。

我个人不喜欢你在else子句中抛出异常。我更喜欢在函数开头进行参数检查,然后立即抛出异常。这可以防止将非错误代码嵌套在多个if块中。

有三种类型的例外

  1. 异步异常,如StackOverflow,OutOfMemory和ThreadAborted。它们可以在任何地方发生,无法真正处理
  2. 像ArgumentException这样的Bug异常,将它们记录在顶级处理程序中并修复错误
  3. 指示可在本地处理的错误的预期异常。通常,当错误不常见时使用这些,并且您无法事先知道操作将导致错误。 IO错误就是一个典型的例子。
    原因通常是外部的。例如,您尝试解析的文件中存在无法访问的文件,网络故障或无效数据。
  4. Eric Lippert在博客文章中讨论了这类异常:Vexing exceptions

    何时使用第三种异常,何时使用返回值是判断调用。

答案 1 :(得分:1)

我不认为这是“不使用例外来控制流量”的情况。

这是参数验证。该方法无法正常工作输入参数无效,因此,使用异常是告诉调用者它正在尝试做坏事的最佳方法。

答案 2 :(得分:1)

如果是意外运行时错误,就像这里一样,它是一个例外,否则它应该是一个返回值。模拟你对int.Parse(throws)/ int.TryParse(返回值)的决定,第一个是你知道事情必须是int的情况(例如解析一个类型化的结构),另一个是用于验证用户输入(输入错误)期待用户)。

异常处理在抛出时运行时很昂贵,应该在循环中避免。

答案 3 :(得分:1)

不应该询问某些东西是应该返回一个值还是抛出一个异常,而应该问一个函数承诺做什么。如果一个函数承诺移动一个节点,它应该抛出异常,如果它不能。如果函数承诺在可能的情况下移动节点,或者通过返回值指示它不能被移动,但是如果数据结构在某种程度上超出了移动节点能力所暗示的基本损坏,则只抛出异常,它应该这样做。有时提供“做它”和“尝试它”变种的功能会很有用。

至于抛出什么类型的异常,我坦率地不喜欢从用户代码中抛出大多数内置异常类型的概念,因为没有很好的编程方式来判断ArgumentException是从例程抛出还是从某些例程抛出这是由您的例程调用的,并且大多数例外都没有说明底层数据结构的质量。

如果有人试图例如从磁盘解析文件并将其与现有数据结构集成并抛出异常,无论该异常是ArgumentException,还是SubscriptOutOfBoundsException,DiskReadErrorException等等都无关紧要。最重要的是解析尝试是否以使数据结构有效的方式回滚;次要的是,是否有可能以另一种方式或在其他情况下解析文件。异常的类型实际上只对它能够回答前两个问题的程度很重要。