C# 中的异常处理

异常是在程序执行期间出现的问题。C# 中的异常是对程序运行时出现的特殊情况的一种响应,比如尝试除以零。

异常提供了一种把程序控制权从某个部分转移到另一个部分的方式。C# 异常处理时建立在四个关键词之上的:try、catch、finally 和 throw。

语法

假设一个块将出现异常,一个方法使用 try 和 catch 关键字捕获异常。try/catch 块内的代码为受保护的代码,使用 try/catch 语法如下所示:

try
{
   // 引起异常的语句
}
catch( ExceptionName e1 )
{
   // 错误处理代码
}
catch( ExceptionName e2 )
{
   // 错误处理代码
}
catch(Exception ex) //捕获所有
{
   // 错误处理代码
}
finally
{
   // 要执行的语句
}
  • try:一个 try 块标识了一个将被激活的特定的异常的代码块。后跟一个或多个 catch 块。
  • catch:程序通过异常处理程序捕获异常。catch 关键字表示异常的捕获。
  • finally:finally 块用于执行给定的语句,不管异常是否被抛出都会执行。例如,如果您打开一个文件,不管是否出现异常文件都要被关闭。
  • throw:当问题出现时,程序抛出一个异常。使用 throw 关键字来完成。

示例

如果您输入非数字字符,以下内容可能会引发异常。 System.FormatException:“Input string was not in a correct format.”

 static void Main(string[] args)
        {
            Console.WriteLine("请输入一个数字 然后回车: ");

            var num = int.Parse(Console.ReadLine());

            Console.WriteLine($"{num * num} =  {num} *  {num}");
        }

现在我加入异常处理

static void Main(string[] args)
{
    Console.WriteLine("请输入一个数字 然后回车: ");
    try
    {
        var num = int.Parse(Console.ReadLine());

        Console.WriteLine($"{num * num} =  {num} *  {num}");
    }
    catch (System.FormatException ex)
    {
        Console.WriteLine("出错了" + ex.ToString());
    }
    finally
    {
        Console.WriteLine("我肯定会被执行");
    }
}

上面的程序当用户输入的是非数字字符的时候。在int.Parse这行会抛出FormatException: 然后程序会跳到catch (System.FormatException ex)里面来。 就会执行 Console.WriteLine("出错了" + ex.ToString()); 当catch的东西执行完之就会继续执行finally里面的代码. finally的代码不管错没错都会执行到。

为了让用户在出错的时候有机会再次输入,我们还得改一下。

static void Main(string[] args)
{
    while (true)
    {
        try
        {
            Console.WriteLine("请输入一个数字 然后回车: ");
            var num = int.Parse(Console.ReadLine());

            Console.WriteLine($"{num * num} =  {num} *  {num}");
            break;
        }
        catch (System.FormatException ex)
        {
            Console.WriteLine("出错了" + ex.ToString());
        }
        finally
        {
            Console.WriteLine("我肯定会被执行");
        }
    }
}

异常过滤器 Exception Filters

一个函数或一段代码有时候会抛出不同的异常。如果我们想对每个异常单独处理,我们就可以写多个catch块,这个称为异常过滤器。

  static void Main(string[] args)
        {
            while (true)
            {
                try
                {
                    Console.WriteLine("请输入一个除数");
                    int num = int.Parse(Console.ReadLine());

                    int result = 100 / num;

                    Console.WriteLine("100 / {0} = {1}", num, result);
                    break;
                }
                catch (DivideByZeroException ex)
                {
                    Console.WriteLine("不能除于0.");
                }
                catch (FormatException ex)
                {
                    Console.WriteLine("不是数字格式. 请重试");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"{ex.Message}! 请重试");
                }
            }

        }

上面的代码中,每一个异常我们都显示不同的错误消息给用户。 注意。不允许具有相同异常类型的多个 catch 块。 捕获 Exception 类型的 catch 块必须是最后一个块, 不然所有的异常都会被它给捕获了。(IDE也会给出错误提示)

Catch错误示例

同一个 try-catch 语句中不允许使用无参数 catch 块和带有 Exception 参数的 catch 块,因为它们都做同样的事情。

try
{
    //可能引发异常的代码
}
catch //不能同时有catch和catch(Exception ex)
{
    Console.WriteLine("发生异常");
}
catch (Exception ex) //不能同时有catch和catch(Exception ex)
{
    Console.WriteLine("发生异常");
}

无参数 catch 块 catch 或一般的 catch 块 catch(Exception ex) 必须是最后一个块。 如果在 catch 或 catch(Exception ex) 块之后还有其他 catch 块,编译器将给出错误。

try
{
    //可能引发异常的代码
}
catch
{
    // 这个catch块必须是最后一个块
}
catch (NullReferenceException nullEx)
{
    Console.WriteLine(nullEx.Message);
}
catch (InvalidCastException inEx)
{
    Console.WriteLine(inEx.Message);
}

finally 块

finally 块是一个可选块,应该在 try 或 catch 块之后。 无论是否发生异常,finally 块都将始终执行。 finally 块通常用于清理代码,例如处理非托管对象。

using System;
using System.IO;
namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 这个类是在System.IO下面。我们上面有用了 using System.IO
            StreamWriter streamWriter = null;
            try
            {
                Console.Write("请输入一个文件地址: ");
                string filePath = Console.ReadLine();
                streamWriter = new StreamWriter(filePath);
                streamWriter.WriteLine("hello");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error occurred: {0}", ex.Message);
            }
            finally
            {
                // 释放文件
                if (streamWriter != null)
                {
                    streamWriter.Dispose();
                }
            }
        }
    }
}

注意 不允许添加多个 finally 块。 此外,finally 块也不能有 return、continue 或 break 关键字。这个跟 finally 块设计不符合。

为了确保释放资源我们通常可以使用using 如下

static void Main(string[] args)
{
    Console.Write("请输入一个文件地址: ");
    string filePath = Console.ReadLine();
    using (var streamWriter = new StreamWriter(filePath))
    { 
        streamWriter.WriteLine("hello");
        //这边有发生异常的话文件也会被关闭。
    }
}

嵌套 try-catch

C# 允许嵌套的 try-catch 块。 使用嵌套的 try-catch 块时,异常发生时, try 块之后的第一个匹配的 catch 块会捕获到这个异常。

static void Main(string[] args)
{
    var divider = 0;

    try
    {
        try
        {
            var result = 100 / divider;
        }
        catch
        {
            Console.WriteLine("内部 catch"); //输出 内部  catch
        }
    }
    catch
    {
        Console.WriteLine("外部 catch");
    }
}

在上面的例子中将执行一个内部 catch 块,因为它是第一个处理所有异常类型的 catch 块。

如果里面的try catch没有捕获到这个异常,则这个异常会继续往外部,直到找到合适的异常过滤器。 考虑以下示例。

static void Main(string[] args)
{
    var divider = 0;

    try
    {
        try
        {
            var result = 100/divider;
        }
        catch(NullReferenceException ex)
        {
            Console.WriteLine("内部 catch");
        }
    }
    catch
    {
        Console.WriteLine("外部 catch"); //输出 外部  catch
    }
}

在上面的示例中,将引发 DivideByZeroException 类型的异常。 因为内部 catch 块只捕获 NullReferenceTypeException,所以它会被外部 catch 块捕获到。

最近更新的
...