一、引言
1、什么是异常
- 异常是程序运行时发生的不正常或错误情况。
- 异常会打断程序的正常流程,导致程序终止或产生不可预测的结果。
2、为什么要处理异常
- 提高程序的健壮性和稳定性。
- 提供友好的错误提示,提升用户体验。
- 方便开发者定位和修复问题
二、C#中的异常处理机制
1、try…catch语句块
- try块:包含可能引发异常的代码。
- catch块:用于捕获并处理特定类型的异常。
2、try…catch…finally
- finally块:无论是否发生异常都会执行的代码块,常用于资源释放。
3、异常类型
3.1.、System.Exception
是所有异常类的基类,它包含了异常的基本信息,如消息、堆栈跟踪等。
3.2、 System.SystemException
它是系统定义的异常类的基类,通常由系统抛出。
ArgumentException
当向方法的参数传递了一个不合法或不适当的值时引发的异常。
ArgumentNullException
当向方法的参数传递了一个空引用(null
)时引发的异常。
ArgumentOutOfRangeException
当向方法的参数传递了一个超出其有效范围的值时引发的异常。
ArithmeticException
在算术运算或类型转换期间发生错误时引发的异常。
IndexOutOfRangeException
当尝试访问数组或集合的无效索引时引发的异常。
InvalidCastException
当尝试执行无效的强制类型转换时引发的异常。
InvalidOperationException
当对象处于无法执行请求的操作的状态时引发的异常。
NullReferenceException
当应用程序尝试在需要对象的情况下使用空引用时引发的异常。
NotSupportedException
当调用某个对象不支持的操作或方法时引发的异常。
FormatException
当字符串的格式不正确时引发的异常。
3.3、 System.ApplicationException
它是用户定义的异常类的基类,通常由应用程序抛出。
三、异常处理的基本用法
1、捕获和处理异常
- 使用try-catch语句块捕获异常。
- 在catch块中处理异常,如记录日志、显示错误消息等。
try
{
int result = 10 / 0; // 这里会引发DivideByZeroException异常
}
catch (DivideByZeroException ex)
{
Console.WriteLine("除数不能为零: " + ex.Message);
}
2、使用多个catch块捕获不同异常
- 可以编写多个catch块来捕获不同类型的异常。
try
{
// 这里可能引发不同类型的异常
}
catch (DivideByZeroException ex)
{
Console.WriteLine("除数不能为零: " + ex.Message);
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine("索引越界: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("发生未知异常: " + ex.Message);
}
3、使用finally块释放资源
- finally块中的代码无论是否发生异常都会执行。
FileStream fs = null;
try
{
fs = new FileStream("example.txt", FileMode.Open);
// 操作文件...
}
catch (IOException ex)
{
Console.WriteLine("文件操作异常: " + ex.Message);
}
finally
{
if (fs != null)
{
fs.Close(); // 确保文件流被关闭
}
}
4、throw关键字
throw
关键字用于显式地抛出异常。这可以在检测到错误条件时,或者想在方法内部重新抛出捕获的异常时使用。throw
关键字后面通常跟着一个异常对象,该对象描述了发生的错误。
4.1、直接抛出异常
当确定某处代码存在问题时,可以直接抛出一个异常。例如:
if (someCondition)
{
throw new InvalidOperationException("Some condition is not met.");
}
4.2、在catch块中重新抛出异常
有时,可能想捕获一个异常,执行一些清理操作,然后再次抛出该异常。这可以通过在catch
块中使用throw;
(没有异常对象)来实现,它将重新抛出最近捕获的异常。
try
{
// ... 一些可能会抛出异常的代码 ...
}
catch (Exception ex)
{
// 执行一些清理操作
// ...
// 重新抛出异常
throw;
}
4.3、抛出捕获的异常并添加信息
如果想在重新抛出异常时添加更多信息,可以捕获异常,创建一个新的异常对象,并将原始异常作为内部异常(InnerException)传递。
try
{
// ... 一些可能会抛出异常的代码 ...
}
catch (Exception ex)
{
// 添加更多信息,并创建一个新的异常对象
throw new InvalidOperationException("An error occurred during processing.", ex);
}
4.4、抛出特定的异常类型
如果知道某个错误条件应该抛出特定类型的异常,可以直接创建并抛出该类型的异常对象。
if (someParameter == null)
{
throw new ArgumentNullException(nameof(someParameter), "The parameter cannot be null.");
}
4.5、在finally块中抛出异常
虽然不常见,但在某些情况下,可能需要在finally
块中抛出异常。但请注意,如果try
或catch
块中已经抛出了异常,并且finally
块中又抛出了新的异常,那么原始异常将被丢失,除非在finally
块中显式地保留它。通常最好避免在finally
块中抛出异常,因为它可能会导致代码逻辑复杂且难以维护。
5、自定义异常
5.1、创建自定义异常类
- 继承自
Exception
或其子类。 - 可以添加自定义属性或方法。
public class CustomBusinessException : Exception
{
public CustomBusinessException(string message) : base(message)
{
}
// 可以添加自定义属性或方法...
}
5.2、抛出和使用自定义异常
- 使用
throw
关键字抛出自定义异常。 - 在catch块中捕获并处理自定义异常。
try
{
throw new CustomBusinessException("自定义业务异常");
}
catch (CustomBusinessException ex)
{
Console.WriteLine("自定义业务异常: " + ex.Message);
}
四、全局异常捕获
在winform或WPF等程序中,有时会出现程序闪退或者崩溃现象,但又无法定位问题,这个时候可能就需要进行一个全局异常捕获来定位问题。下面介绍几个常用的全局异常捕获方式。
1、在Program.cs里简单粗暴的try…catch
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmMain());
}
catch (Exception ex)
{
MessageBox.Show(string.Format("捕获到未处理异常:{0}\r\n异常信息:{1}\r\n异常堆栈:{2}", ex.GetType(), ex.Message, ex.StackTrace));
}
}
2、更优雅的事件监听:Application类中的ThreadException事件
Application类负责控制整个Windows 程序的运行。Application类的事件ThreadException是在发生未捕获线程异常时发生。
static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.ThreadException += Application_ThreadException;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmMain());
}
static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
Exception ex = e.Exception;
MessageBox.Show(string.Format("捕获到未处理异常:{0}\r\n异常信息:{1}\r\n异常堆栈:{2}", ex.GetType(), ex.Message, ex.StackTrace));
}
}
3、子线程异常捕获AppDomain.CurrentDomain.UnhandledException
上面的Application类中的ThreadException事件来捕获异常只能捕获到主线程的异常,如果在我们的程序中使用了多线程技术并且子线程发生了异常,ThreadException事件就无法进行捕获,如果要监听子线程的异常,我们就需要再注册一个事件:AppDomain.CurrentDomain.UnhandledException这个事件是在当前程序域内发生未处理异常时才会发生(如果没有监听Application.ThreadException事件的话,主线程异常最终也会触发这个事件)
static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.ThreadException += Application_ThreadException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmMain());
}
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = e.ExceptionObject as Exception;
MessageBox.Show(string.Format("捕获到未处理异常:{0}\r\n异常信息:{1}\r\n异常堆栈:{2}\r\nCLR即将退出:{3}", ex.GetType(), ex.Message, ex.StackTrace, e.IsTerminating));
}
static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
Exception ex = e.Exception;
MessageBox.Show(string.Format("捕获到未处理异常:{0}\r\n异常信息:{1}\r\n异常堆栈:{2}", ex.GetType(), ex.Message, ex.StackTrace));
}
}
4、Web程序全局异常捕获
4.1、Global.asax中编写Application_Error函数
在ASP.NET Web Forms应用程序中,全局异常捕获通常通过在Global.asax
文件中编写Application_Error
事件处理器来实现。Global.asax
是一个特殊的ASP.NET文件,它包含应用程序级别和会话级别的事件处理程序,用于响应ASP.NET应用程序或会话级别的事件。
下面是如何在Global.asax
中编写Application_Error
事件处理器来捕获全局异常的示例:
using System;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public class Global : System.Web.HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
// 在应用程序启动时运行的代码
}
void Application_End(object sender, EventArgs e)
{
// 在应用程序关闭时运行的代码
}
void Application_Error(object sender, EventArgs e)
{
// 获取当前的HTTP上下文
HttpContext context = HttpContext.Current;
// 如果存在未处理的异常
if (context.Error != null)
{
// 获取异常对象
Exception exception = context.Error;
// 这里可以记录异常到日志系统
// 例如:使用NLog, log4net, 或者System.Diagnostics.Trace等
// 清除错误,以便不会再次触发
context.Server.ClearError();
// 将用户重定向到一个友好的错误页面
context.Response.Redirect("~/ErrorPage.aspx", true);
// 或者你也可以直接将错误信息写入响应中(不推荐在生产环境中使用)
// context.Response.Write("An error occurred: " + exception.Message);
// context.Response.End();
}
}
// 其他事件处理器...
}
在上面的代码中,Application_Error
事件处理器会捕获在ASP.NET应用程序中发生的任何未处理的异常。在事件处理器内部,可以通过HttpContext.Current.Error
属性访问异常对象。然后,可以将异常记录到日志系统、清除错误状态以避免重复触发事件、以及将用户重定向到一个友好的错误页面。
需要注意的是,使用Server.ClearError()
方法来清除错误是很重要的,这样就不会再次触发Application_Error
事件。如果不这样做,可能会导致无限循环或不可预测的行为。
4.2、使用Filter进行全局异常捕获
在ASP.NET Web Forms应用程序中,通常使用Global.asax
的Application_Error
事件处理器来进行全局异常捕获。然而,在ASP.NET MVC中,可以使用过滤器(Filter)来实现类似的功能。过滤器允许在请求的不同阶段执行自定义逻辑,包括异常处理。
下面是如何使用过滤器在ASP.NET MVC中进行全局异常捕获的示例:
首先,创建一个实现IExceptionFilter
接口的自定义异常过滤器:
using System;
using System.Web.Mvc;
public class GlobalExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
// 这里可以记录异常到日志系统
// 例如:使用NLog, log4net, 或者System.Diagnostics.Trace等
// 如果需要,可以修改HTTP响应
filterContext.HttpContext.Response.StatusCode = 500; // 内部服务器错误
filterContext.ExceptionHandled = true; // 标记异常已被处理
// 将用户重定向到一个友好的错误页面,或者返回自定义的错误响应
filterContext.Result = new RedirectResult("~/Error/Index"); // 假设有一个ErrorController和Index Action
// 或者返回一个ViewResult, JsonResult等,取决于你的需求
}
}
然后,你需要在全局范围内注册这个过滤器。这通常是在Global.asax
文件的Application_Start
事件或者MvcApplication
类中完成的(取决于你的项目结构):
using System.Web.Mvc;
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
// 注册全局异常过滤器
GlobalFilters.Filters.Add(new GlobalExceptionFilter());
}
}
或者,如果你有一个单独的FilterConfig
类来配置过滤器,你可以在那里添加你的全局异常过滤器:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute()); // 默认的异常处理过滤器(可选)
filters.Add(new GlobalExceptionFilter()); // 你的自定义全局异常过滤器
}
}
现在,每当MVC应用程序中发生未处理的异常时,GlobalExceptionFilter中的
OnException方法就会被调用,我们可以在这里记录异常、修改HTTP响应,或者将用户重定向到一个友好的错误页面。
请注意,ASP.NET MVC还提供了一个内置的HandleErrorAttribute
,它可以用来处理控制器或操作中的异常,并将其重定向到一个错误视图。然而,HandleErrorAttribute
通常不如自定义的IExceptionFilter
灵活,特别是在需要执行复杂的错误处理逻辑时。
最后,对于ASP.NET Web API项目,你可以使用IExceptionHandler
接口来实现类似的异常处理逻辑。在Web API中,你通常会创建一个实现IExceptionHandler
的类,并在GlobalConfiguration.Configuration.Services.Replace
中注册它。
五、注意事项
在Windows程序里,如果同时监听了Application.ThreadException事件和AppDomain.CurrentDomain.UnhandledException事件的话,则异常优先被Application.ThreadException事件捕获。但Application.ThreadException事件只能监听程序主线程抛出的异常。
在Web程序里,如果同时使用了Global.asax中编写Application_Error函数进行全局异常捕获和使用Filter进行全局异常捕获,异常优先被Filter捕获。
标签:C#,捕获,System,笔记,Application,ex,catch,异常 From: https://blog.csdn.net/weixin_63658665/article/details/136896657