C# 异常与特殊返回值

C# 异常与特殊返回值,c#,.net,exception,C#,.net,Exception,哪一个是编程中更好的实践 我不是说完全的排他性。它更多地用于以下方面: list.Find,从中获取default(T)或您的值,而不是ValueNotFound异常(示例) 或 list.IndexOf,从中可以得到-1或正确的索引。答案是这取决于具体情况 在列表中找不到项的情况下,抛出异常是一种可怕的做法,因为该项不在列表中是完全可行的 现在,如果这是某种特殊的列表,并且该项应该绝对地在列表中找到,那么应该抛出一个异常,因为您遇到了不可行/不合理的情况 一般来说,对于业务规则之类的事情,专门

哪一个是编程中更好的实践

我不是说完全的排他性。它更多地用于以下方面:

list.Find
,从中获取
default(T)
或您的值,而不是
ValueNotFound
异常(示例)


list.IndexOf
,从中可以得到-1或正确的索引。

答案是这取决于具体情况

在列表中找不到项的情况下,抛出异常是一种可怕的做法,因为该项不在列表中是完全可行的

现在,如果这是某种特殊的列表,并且该项应该绝对地在列表中找到,那么应该抛出一个异常,因为您遇到了不可行/不合理的情况


一般来说,对于业务规则之类的事情,专门的错误代码更好,因为您知道这些事情发生的可能性,并且希望对这些可能性做出反应。异常适用于您不期望的情况,如果发生异常,则无法继续执行代码。

从纯设计角度来看,我更喜欢在发生“异常”事件时抛出异常,例如,未找到您请求的密钥。主要原因是它使消费者的生活更加轻松-他们不必在每次调用函数后检查值。我还喜欢TrySomething函数,因为这样用户就可以显式地测试操作是否成功


不幸的是,.Net中的异常非常昂贵(根据我的经验,抛出一个异常通常需要50毫秒),因此从实际角度来看,您可能希望返回这些默认值中的一个,而不是抛出一个异常,尤其是在GUI编程中。

我认为这有点像情况,但更多地取决于返回事件在逻辑上是否是异常。indexOf示例是一个完全正常的输出响应,其中-1是唯一有意义的(对于ref类型为null)


异常应该是:exception,这意味着您需要从实际异常中获得的所有堆栈跟踪和处理。

在您提到的情况下,我更喜欢返回值,因为我知道如何处理它。而且没有理由在同一范围内为捕获而烦恼


当然,如果您的逻辑是以值总是应该在列表中的方式构建的,并且它们的缺失是程序员的逻辑错误-异常就是一种方式。

来自Java背景,在大多数情况下,我更喜欢异常。当一半的代码没有花在检查返回值上时,它会使代码更加干净


也就是说,这还取决于某件事情可能导致“失败”的频率。异常可能很昂贵,因此您不希望为了经常失败的事情而不必要地丢弃它们。

异常应该是“异常”的东西。所以,如果您调用Find,并且无论发生什么,您都希望找到某个东西,那么如果您没有找到某个东西,则抛出异常是良好的行为

然而,如果是正常的流程,即有时找不到什么东西,那么抛出异常是不好的。

更有效的C#由Bill Wagner提出的关于异常的极好建议。这样做的目的是在执行操作时抛出异常,只需确保提供挂钩,以检查该值是否会抛出异常

比如说

// if this throws an exception
list.GetByName("Frank") // throws NotFound exception
// provide a method to test 
list.TryGetByName("Frank")  // returns false
这样,您可以通过编写以下内容来选择退出异常

MyItem item;
if (list.TryGetByName("Frank"))
  item = list.GetByName("Frank");

经验法则是只有在“不应该发生”的事情发生时才使用异常

如果您期望对IndexOf()的调用可能找不到有问题的值(这是一个合理的期望),那么它应该有一个返回代码(如您所说,可能是-1)。一些永远不会失败的事情,比如分配内存,应该在失败的情况下抛出异常


另一件需要记住的事情是,处理异常在性能方面是“昂贵的”。因此,如果您的代码作为正常操作的一部分定期处理异常,那么它的执行速度就不会那么快。

您可能会喜欢我的两部分博客系列,其中根据您的编程语言支持的功能讨论了许多优缺点,因为它似乎非常相关:

我要补充的是,我认为这个问题的很多答案都很差(我在我的许多同伙中都投了反对票)。尤其糟糕的是,API与

if (ItWillSucceed(...)) {
    DoIt(...)
}

这会产生各种各样的不愉快的问题(详情请参见我的博客)。

正如许多与编程相关的问题一样,这一切都取决于

我发现,人们确实应该首先尝试定义API,以便在一开始就不会发生异常情况

使用契约式设计可以帮助做到这一点。这里可以插入抛出错误或崩溃并指示编程错误(而不是用户错误)的函数。(在某些情况下,这些检查将在释放模式下删除。)

然后为无法避免的一般故障保留例外,如DB连接失败、乐观事务失败、磁盘写入失败

这些异常通常在到达“用户”之前不需要捕获。并将导致用户需要重试

如果该错误是用户错误,例如名称或其他内容中的键入错误,则直接在应用程序接口代码本身中处理该错误。由于这是一个常见错误,因此需要使用用户友好的错误消息进行处理

应用程序分层在这里也很有用。因此,让我们以将现金从一个账户转移到另一个账户为例:

transferInternal( int account_id_1, int account_id_2, double amount )
{
   // This is an internal function we require the client to provide us with
   // valid arguments only. (No error handling done here.)
   REQUIRE( accountExists( account_id_1 ) ); // Design by contract argument checks.
   REQUIRE( accountExists( account_id_2 ) );
   REQUIRE( balance( account_id_1 ) > amount );
   ... do the actual transfer work
}

string transfer( int account_id_1, int account_id_2, double amount )
{
   DB.start(); // start transaction
   string msg;
   if ( !checkAccount( account_id_1, msg ) ) return msg; // common checking code used from multiple operations.
   if ( !checkAccount( account_id_2, msg ) ) return msg;
   if ( !checkBalance( account_id_1, amount ) ) return msg;
   transferInternal( account_id_1, account_id_2, amount );
   DB.commit(); // This could fail with an exception if someone else changed the balance while this transaction was active. (Very unlikely but possible)
   return "cash transfer ok";
}

我在某个地方读到一条很好的规则,我非常喜欢。它说:“一个函数应该抛出一个异常,当且仅当它不能执行它想要执行的任务时”

所以我通常做的是决定什么是函数
item = list.Find(x);
If (list.Contains(x))
    item = list.Find(x);
else
    item = null;
try {
   item = list.Find(x);
}
catch {
     item = null;
}
user := userDictionary at: id
user := userDictionary at: id ifAbsent: [
    "no such id, let's return the user named Anonymous"
    users detect: [ :each | each name = 'Anonymous' ] ]
class Maybe<T> {
    public Maybe() {
        set = false;
        value = null;
    }

    public Maybe(T value) {
        set = true;
        this.value = value;
    }

    public T get(T defaultVal) {
        if (set)
            return value;
        return defaultVal;
    }

    private boolean set;
    private T value;
}
public class Option<T>
{
    string _msg = "";
    T _item;

    public bool IsNone
    {
        get { return _msg != "" ? true : false; }
    }

    public string Msg
    {
        get { return _msg; }
    }

    internal Option(T item)
    {
        this._item = item;
        this._msg = "";
    }

    internal Option(string msg)
    {
        if (String.IsNullOrEmpty(msg))
            throw new ArgumentNullException("msg");

        this._msg = msg;
    }

    internal T Get()
    {
        if (this.IsNone)
            throw new Exception("Cannot call Get on a NONE instance.");

        return this._item;
    }

    public override string ToString()
    {
        if (this.IsNone)
            return String.Format("IsNone : {0}, {1}", this.IsNone, typeof(T).Name);

        else 
            return String.Format("IsNone : {0}, {1}, {2}", this.IsNone, typeof(T).Name, this._item.ToString());
    }
var optionItem = list.TryFind(x => trueorFalseTest() );
if (!optionItem.IsNone)
   var myItem = optionItem.Get();