.NET漫游指南-012-优雅的捕获并处理异常

在.NET的开发中,尤其是前端的开发中,经常会遇到服务中的数据异常,返回字段异常,异步处理中引用对象为空

数据类型无法进行转换等,所有的异常都要进行捕获处理,如果直接反馈给客户端是非常的不友好的一种提样,下

面总结一下异常的捕获和处理。

1:throw , try - catch , try - catch -finally

以上三种是是我们经常使用的异常捕获和处理方式。

其中:throw的语句结构最为简单,throw [e] ,e 表示的是各种异常类型。我们可以自己在程序中按照程序的处理

逻辑来处理程序的异常。

using System;

public class NumberGenerator
{
   int[] numbers = { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
   
   public int GetNumber(int index)
   {
      if (index < 0 || index >= numbers.Length) {
         throw new IndexOutOfRangeException();
      }
      return numbers[index];
   }
}

我们可以自己在方法中添加上异常抛出的语句。

using System;

public class Example
{
   public static void Main()
   {
      var gen = new NumberGenerator();
      int index = 10;
      try {
          int value = gen.GetNumber(index);
          Console.WriteLine($"Retrieved {value}");
      }
      catch (IndexOutOfRangeException e)
      {
         Console.WriteLine($"{e.GetType().Name}: {index} is outside the bounds of the array");
      }
   }
}
// The example displays the following output:
//        IndexOutOfRangeException: 10 is outside the bounds of the array

也可在用try - catch语句结构来处理并抛出异常。

using System;

public class Sentence
{
   public Sentence(string s)
   {
      Value = s;
   }

   public string Value { get; set; }

   public char GetFirstCharacter()
   {
      try {
         return Value[0];
        }
      catch (NullReferenceException e) {
         throw;   
      }
   }
}

public class Example
{
   public static void Main()
   {
      var s = new Sentence(null);
      Console.WriteLine($"The first character is {s.GetFirstCharacter()}");
   }
}
// The example displays the following output:
//    Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
//       at Sentence.GetFirstCharacter()
//       at Example.Main()

重复抛出异常,在实际开发中我们很少在catch block中直接对异常进行处理,尤其是在公共函数中的try - catch处理语句。

如果直接在catch block中进行了处理,那么调用这个公共函数的函数就无法对异常进行正确的处理,程序在异常的情况下

继续运行,所以这个时候就需要使用throw来将异常抛给上层函数,同时结束该函数的运行(如果是异常的最后一站,那

么肯定不能抛出,要进行提示,而非异常抛出)。

注意:从C#7开始,throw 表达式就可以像普通语句那样使用并在其中抛出自定义的异常提示语句,这在之前是不允许的。

private static void DisplayFirstNumber(string[] args)
{
   string arg = args.Length >= 1 ? args[0] :
                              throw new ArgumentException("You must supply an argument");
   if (Int64.TryParse(arg, out var number))
      Console.WriteLine($"You entered {number:F0}");
   else
      Console.WriteLine($"{arg} is not a number.");                            
 
}

上面的语句中,throw像一个平常的语句进行了使用,在C#7之前这种处理逻辑是必须放在if-else语句中的。


在try - catch中,try里面是正常的程序处理逻辑,后面会跟一个或者多个catch逻辑处理块,来处理不同的异常。如果使用了

catch clauses 就要注意catch的使用顺序,因为catch的运行是有一定顺序的。

catch (ArgumentException e) when (e.ParamName == "…")  
{  
}

catch (FileNotFoundException e)  
{  
    // FileNotFoundExceptions are handled here.  
}  
catch (IOException e)  
{  
    // Extract some information from this exception, and then   
    // throw it to the parent method.  
    if (e.Source != null)  
        Console.WriteLine("IOException source: {0}", e.Source);  
    throw;  
}

如上所示,为了更进一步的筛选出程序需要处理的异常,我们甚至可以采用添加一个谓词的方法来进行过滤。

异常过滤最好捕获并抛出异常,因为过滤器会保留stack原来的状态,当后来的处理者来处理这个stack的转储时

就可以知道异常的source,从而可以获得更多的异常信息。


异步方法中的异常

一个异步的方法是被异步进行修改的,并且总是包含一个或者多个等待的表达式或者语句。一个await表达式应用await

操作符于Task或者Task<TRsult>。当控制流程执行到一个异步method中的await时,该方法中的程序预期会暂停,直到awaited task完成。当task完成时,运行程序会恢复这个method的运行。、

当await应用的task执行完毕时,task可能会由于await返回一个无法处理的异常结果而处于故障状态。一个task也可能会以取消的状态终止,如果异步程序返回一个取消的结果。所以为了捕获可能产生的异常,await the task一般被写在try block中。下面是摘录的实例代码:

public async Task DoSomethingAsync()
{
    Task<string> theTask = DelayAsync();

    try
    {
        string result = await theTask;
        Debug.WriteLine("Result: " + result);
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Exception Message: " + ex.Message);
    }
    Debug.WriteLine("Task IsCanceled: " + theTask.IsCanceled);
    Debug.WriteLine("Task IsFaulted:  " + theTask.IsFaulted);
    if (theTask.Exception != null)
    {
        Debug.WriteLine("Task Exception Message: "
            + theTask.Exception.Message);
        Debug.WriteLine("Task Inner Exception Message: "
            + theTask.Exception.InnerException.Message);
    }
}

private async Task<string> DelayAsync()
{
    await Task.Delay(100);

    // Uncomment each of the following lines to
    // demonstrate exception handling.

    //throw new OperationCanceledException("canceled");
    //throw new Exception("Something happened.");
    return "Done";
}

// Output when no exception is thrown in the awaited method:
//   Result: Done
//   Task IsCanceled: False
//   Task IsFaulted:  False

// Output when an Exception is thrown in the awaited method:
//   Exception Message: Something happened.
//   Task IsCanceled: False
//   Task IsFaulted:  True
//   Task Exception Message: One or more errors occurred.
//   Task Inner Exception Message: Something happened.

// Output when a OperationCanceledException or TaskCanceledException
// is thrown in the awaited method:
//   Exception Message: canceled
//   Task IsCanceled: True
//   Task IsFaulted:  False
下面的例子展示一个处理multiple task 的异常方法,其中try block 会等到tasks全部完成以后再执行。每一个task都可能会产生异常。

public async Task DoMultipleAsync()
{
    Task theTask1 = ExcAsync(info: "First Task");
    Task theTask2 = ExcAsync(info: "Second Task");
    Task theTask3 = ExcAsync(info: "Third Task");

    Task allTasks = Task.WhenAll(theTask1, theTask2, theTask3);

    try
    {
        await allTasks;
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Exception: " + ex.Message);
        Debug.WriteLine("Task IsFaulted: " + allTasks.IsFaulted);
        foreach (var inEx in allTasks.Exception.InnerExceptions)
        {
            Debug.WriteLine("Task Inner Exception: " + inEx.Message);
        }
    }
}

private async Task ExcAsync(string info)
{
    await Task.Delay(100);
    
    throw new Exception("Error-" + info);
}

// Output:
//   Exception: Error-First Task
//   Task IsFaulted: True
//   Task Inner Exception: Error-First Task
//   Task Inner Exception: Error-Second Task
//   Task Inner Exception: Error-Third Task

try - finally

在finally block中,可以处理所有try block中引发的异常,并保证程序的继续运行。所以在处理异常时,关联使用finally block能够保证程序的运行。同理try-catch-finally也是最终一定会执行finally里面的语句。


  • Add to Phrasebook
    • No wordlists for English -> Simplified Chinese...
    • Create a new wordlist...
  • Copy
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值