C# 异常处理 详解

总目录


前言


一、异常

1、定义

异常是在程序执行期间出现的问题。

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

异常:程序员必须控制和解决的问题。程序可以正常的运行,但是在运行的过程中出现了问题,问题包括:非法数值,死循环,数组超出索引,访问空对象,格式化转换错误...等等,异常是程序员必须处理的。

异常具有以下属性:

  • 异常是最终全都派生自 System.Exception 的类型。
  • 在可能抛出异常的语句周围使用 try 代码块。
  • 在 try 代码块中出现异常后,控制流会跳转到调用堆栈中任意位置上的首个相关异常处理程序。 在 C# 中,catch 关键字用于定义异常处理程序。
  • 如果给定的异常没有对应的异常处理程序,那么程序会停止执行,并显示错误消息。

2、基本使用

C# 异常处理是建立在四个关键词之上的:try、catch、finally 和 throw。

  • try:try 代码段,表示尝试运行一段代码,如果出现异样,将控制权交给catch。try后可跟一个或多个 catch 块。
  • catch:程序通过异常处理程序捕获异常。catch 关键字表示异常的捕获。
  • finally:finally 块用于执行给定的语句,不管异常是否被抛出都会执行。如:打开一个文件,不管是否出现异常文件都要被关闭。
  • throw:当问题出现时,程序抛出一个异常。使用 throw 关键字来完成。

异常处理格式如下:

csharp 复制代码
	try
	{
 	 	// 引起异常的语句
	}
	catch( ExceptionName e1 ) //try后可以跟一个或多个catch
	{
		throw new Exception("****"); //使用throw 抛出异常信息
	}
	catch( ExceptionName e2 )
	{
   		// 错误处理代码
	}
	finally //finally不是必须的,根据业务需要使用
	{
   		// 要执行的语句 ,不论是否异常都会执行这里的代码
	}

3、异常类

  • System.ApplicationExceptionSystem.SystemException 类是派生于 System.Exception 类的异常类。
  • System.ApplicationException 类支持由应用程序生成的异常。所以程序员定义的异常都应派生自该类。
  • System.SystemException 类是所有预定义的系统异常的基类。

案例代码如下:

csharp 复制代码
        public static void Division(int num1, int num2)
        {
            var result = 0;
            try
            {
                result = num1 / num2;
            }
            catch (DivideByZeroException e)
            {
                Console.WriteLine($"【异常信息】:{e.Message}");
                Console.WriteLine($"【异常代码行】:{e.StackTrace}");
                Console.WriteLine($"【异常所有信息】: {e}");    
            }
            finally
            {
                Console.WriteLine($"Result: {result}");
            }
        }

        static void Main(string[] args)
        {
            Program.Division(18,0);
            Console.ReadLine();
        }

4、自定义异常

定义自己的异常。用户自定义的异常类是派生自 ApplicationException 类。

csharp 复制代码
using System;
namespace UserDefinedException
{
   class TestTemperature
   {
      static void Main(string[] args)
      {
         Temperature temp = new Temperature();
         try
         {
            temp.showTemp();
         }
         catch(TempIsZeroException e)
         {
            Console.WriteLine("TempIsZeroException: {0}", e.Message);
         }
         Console.ReadKey();
      }
   }
}
public class TempIsZeroException: ApplicationException
{
   public TempIsZeroException(string message): base(message)
   {
   }
}
public class Temperature
{
   int temperature = 0;
   public void showTemp()
   {
      if(temperature == 0)
      {
         throw (new TempIsZeroException("Zero Temperature found"));
      }
      else
      {
         Console.WriteLine("Temperature: {0}", temperature);
      }
   }
}

二、异常处理

csharp 复制代码
        static void Main(string[] args)
        {
            Program.Division0(18,0);
            Console.Read();
        }

        public static void Division0(int num1, int num2)
        {
            var result = num1 / num2;
            Console.WriteLine($"Result: {result}");
        }

这是一个存在异常情况的代码,当我们不使用try catch 来捕获异常的时候,运行该程序会直接显示异常,然后关闭程序。

1、不抛出异常

使用try catch 处理异常,程序内部处理掉,不使用 throw 抛出异常。

csharp 复制代码
        static void Main(string[] args)
        {
            Program.Division(18,0);
            Console.Read();
        }

        public static void Division(int num1, int num2)
        {
            var result = 0;
            try
            {
                result = num1 / num2;
            }
            catch (Exception e)
            {
                Console.WriteLine($"【异常信息】: {e}");
            }
            finally
            {
                Console.WriteLine($"Result: {result}");
            }
        }

运行结果:打印错误信息,程序不会因为异常而关闭。

某些场景下,我们需要保证用户的使用体验,选择不抛出异常,大多是上线后,这时基本没什么Bug了

2、抛出异常

使用try catch 处理异常,使用 throw 抛出异常。

csharp 复制代码
        static void Main(string[] args)
        {
            Program.Division(18,0);
            Console.Read();
        }

        public static void Division(int num1, int num2)
        {
            var result = 0;
            try
            {
                result = num1 / num2;
            }
            catch (Exception e)
            {
                Console.WriteLine($"【异常信息】: {e}");
                throw;
            }
            finally
            {
                Console.WriteLine($"Result: {result}");
            }
        }

运行结果:打印错误信息,程序会因为抛出的异常而关闭。

某些场景下,我们需要抛出异常。大多是开发过程中,通过抛出的异常,我们可以更好的定位和修复异常,完善我们开发的程序。

3、throw、throw ex、throw new Exception 的区别

在C#中引发异常的三种常见方法是throw、throw ex 和 throw new Exception(ex),尽管它们看起来很相似,但它们都有不同的行为和实例。

1. 案例

使用throw / throw ex / throw new Exception 抛出异常,然后使用try catch 捕获异常,查看三者抛出的异常信息有什么区别

  • 使用 throw 抛出异常

    运行结果

    使用throw 抛出的异常,再次被捕获的时候,会将错误信息定位在throw 的那一行,而非真正引发异常的 result=num1/num2 那一行。
    如果需要获取,真正的错误信息,就需要在catch中直接先写入日志,再抛出异常,这样有利于我们异常快速定位!

  • 使用throw ex 抛出异常

    运行结果

    使用throw ex 抛出的异常,再次被捕获的时候,会将错误信息定位在throw ex的那一行,而非真正引发异常的 result=num1/num2 那一行。
    如果需要获取,真正的错误信息,就需要在catch中直接先写入日志,再抛出异常,这样有利于我们异常快速定位!

  • 使用throw new Exception 抛出异常

    运行结果

    使用 throw new Exception 抛出的异常,再次被捕获的时候,会有相较于throw 和 throw ex 更为详细的异常信息(列出了导致异常的所有的信息)

2. 进一步 案例

  • 上面的案例仅仅是展示 使用三种形式抛出异常后 运行结果,那么在实际的开发过程中,有可能会出现try catch 嵌套多层的情况。
  • 多层try catch 嵌套的时候就需要注意 异常的追踪
  • 原则上尽量不要嵌套多层,如果嵌套了多层就需要了解这三种抛异常需要注意的点
csharp 复制代码
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Program.Division2();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"【捕获抛出的异常】:{ex}");
            }
            Console.ReadLine();
        }


        public static void Division2()
        {
            try
            {
                Program.Division(18, 0);
            }
            catch (Exception ex)
            {
                throw;
                //throw ex;
                //throw new Exception("计算异常", ex);
            }
        }


        public static void Division(int num1, int num2)
        {
            var result = 0;
            try
            {
                result = num1 / num2;
            }
            catch (Exception ex)
            {
                throw;
                //throw ex;
                //throw new Exception("计算异常",ex);
            }
        }
    }
  • 案例代码

  • 使用 throw 抛异常运行的结果

  • 使用throw ex 抛异常运行的结果

    注意:这里通过throw ex 抛出异常,重置堆栈跟踪,将原本行号51 的错误信息,重置到了行号 35,不利于我们定位异常信息。

  • 使用throw new Exception 抛异常的结果

    仍旧是具有最全面的异常信息

3. 小结

三者的区别点:

  • 1、堆栈跟踪

    • throw:保留原始堆栈跟踪,提供有关异常来源的完整信息。
    • throw ex:重置堆栈跟踪,这可能会使调试异常的根本原因发生改变。
    • throw new Exception:为新的异常消息提供其他上下文,同时将原始异常保留为内部异常。
  • 2、应用场景

    • throw:需维护原始异常上下文和堆栈跟踪时,使用该方法。
    • throw ex:该方法谨慎,如需使用,应在重新引发异常之前向异常添加其他信息。
    • throw new Exception:如果需要往异常添加更多上下文,同时保留原始异常详细信息,使用该方式。
  • 3、建议

    • 慎用 throw ex 抛出异常
    • 建议使用throw ex 或者 throw new Exception,使用throw new Exception 内存占用会大些但是信息更全面

结语

回到目录页: C# 知识汇总

希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
C#文档 - 异常和异常处理
C#异常处理throw、throw ex、throw new Exception 区别
C# 异常处理

相关推荐
SimonKing1 小时前
OpenCode AI辅助编程,不一样的编程思路,不写一行代码
java·后端·程序员
FastBean1 小时前
Jackson View Extension Spring Boot Starter
java·后端
Kapaseker2 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴2 小时前
Android17 为什么重写 MessageQueue
android
Seven972 小时前
剑指offer-79、最⻓不含重复字符的⼦字符串
java
皮皮林55111 小时前
Java性能调优黑科技!1行代码实现毫秒级耗时追踪,效率飙升300%!
java
冰_河12 小时前
QPS从300到3100:我靠一行代码让接口性能暴涨10倍,系统性能原地起飞!!
java·后端·性能优化
桦说编程15 小时前
从 ForkJoinPool 的 Compensate 看并发框架的线程补偿思想
java·后端·源码阅读
躺平大鹅16 小时前
Java面向对象入门(类与对象,新手秒懂)
java