掌握 C# 语言中的异常处理

科技   2024-11-18 17:07   上海  


处理异常是在 C# 中编写可靠且可维护的应用程序的关键部分。然而,如此多的开发人员仍然陷入陷阱,导致代码难以调试和丢失重要的错误信息。在此博客中,我们将尝试介绍 C# 中异常处理的最佳实践,并介绍现代、可重用的技术,这些技术将使您的代码更简洁、更高效且更易于维护。

为什么异常处理很重要

C# 中的异常是当程序遇到意外情况(如无效输入、网络故障或资源限制)时发生的运行时错误。如果处理不当,异常可能会导致应用程序崩溃、降低用户体验并使调试成为一场噩梦。

正确的异常处理对于以下情况至关重要:

  • 稳定性:防止意外崩溃并确保正常处理错误。

  • 调试:保留有用的信息(如堆栈跟踪)以缩小任何问题的根本原因范围。

  • 安全性:避免在错误消息中暴露敏感信息。

C# 语言中异常处理的最佳实践

1. 只捕捉你能处理的

开发人员常犯的一个错误是捕获异常而没有正确处理它们。如果不知道如何以有意义的方式处理方法中的异常,最好让它冒泡到更高的级别。仅在您可以处理的地方捕获异常。

try  
{
// Code that might throw an exception
}
catch (SpecificException ex)
{
// Handle specific exception, like logging or retrying
}

**避免:**捕获一般异常,除非它位于调用堆栈的顶部,例如在全局错误处理代码中。不加选择地捕获所有内容会使诊断特定问题变得更加困难。

2. 正确重新抛出异常:避免throw ex;

异常处理中的另一个常见错误是在块内使用。许多开发人员不知道这会重置堆栈跟踪,从而更难跟踪最初引发异常的位置。Always use 用于保留原始堆栈跟踪。throw ex;catchthrow;

不對:

catch (Exception ex)  
{
// This resets the stack trace
throw ex;
}

正确:

catch (Exception ex)  
{
// Preserves the original stack trace
throw;
}

通过保留堆栈跟踪,您可以保留有关错误来源的最关键信息,从而更轻松地调试和修复问题。

3. 对特定于域的错误使用自定义例外

如果内置异常(如 or 等)不能准确描述应用程序中的错误,请考虑创建自定义异常。这增加了清晰度,并使您的代码更具可读性。ArgumentNullExceptionInvalidOperationException

public class InvalidOrderException : Exception  
{
public InvalidOrderException(string message) : base(message) { }
}

仅当自定义例外对理解特定域中的问题有真正的价值时,才使用自定义例外。

4. 记录异常,但避免超日志

记录异常是必不可少的,但过度记录可能会使您的日志不堪重负,其中包含不必要的数据。仅当异常提供有价值的见解时才记录异常,并确保不暴露敏感信息。

catch (Exception ex)  
{
// Log the exception with meaningful details
logger.LogError(ex, "Error occurred during operation.");
throw; // Rethrow the exception to maintain the stack trace
}

避免:记录每个小异常或记录过多细节,这可能会淹没您的日志并掩盖真正的问题。过度日志记录也会损害应用程序性能。

5. 使用 ASP.NET Core 中的过滤器进行集中式异常处理

如果您正在构建 ASP.NET Core 应用程序,则可以使用异常筛选条件集中处理异常。这是一种在一个位置处理整个应用程序中错误的现代方法。

示例:全局异常过滤器

public class GlobalExceptionFilter : IExceptionFilter  
{
public void OnException(ExceptionContext context)
{
var exception = context.Exception;
// Handle and log the exception globally
context.Result = new ObjectResult("An internal error occurred")
{
StatusCode = 500
};
context.ExceptionHandled = true;
}
}

在 中全局注册此过滤器 :Startup.cs

public void ConfigureServices(IServiceCollection services)  
{
services.AddControllers(options =>
{
options.Filters.Add<GlobalExceptionFilter>();
});
}

这可确保在整个应用程序中一致地处理所有异常,从而提高应用程序的整体可维护性。

现代异常处理技术

1. 使用函数式编程实现更清晰的异常处理

函数式编程原则可以帮助您编写更简洁、更具可读性的代码。您可以使用高阶函数封装异常处理,从而减少样板代码并使逻辑更具声明性。

使用这种方法可能是个人选择,因为许多开发人员可能不习惯使用它。

public static class Try  
{
public static void Execute(Action action, Action<Exception> handleError)
{
try
{
action();
}
catch (Exception ex)
{
handleError(ex);
}
}
}

用法:

Try.Execute(() =>   
{
// Risky operation
int result = 10 / 0;
}, ex => Console.WriteLine($"Error: {ex.Message}"));

这使您的代码保持简洁,并专注于业务逻辑。

2. 正常处理异步异常

在处理异步代码时,异常处理可能会变得更加棘手。使用或创建异步包装器来有效地管理异步异常。Task.Run

public static async Task SafeExecuteAsync(Func<Task> action, Action<Exception> handleError)  
{
try
{
await action();
}
catch (Exception ex)
{
handleError(ex);
}
}

用法:

await SafeExecuteAsync(async () =>  
{
await SomeAsyncOperation();
}, ex => Console.WriteLine($"Async error: {ex.Message}"));

此方法可确保异步代码中的异常得到与同步代码中一样的正常处理。

3. 使用包装器进行显式错误处理Result

对于更可预测的错误流,请使用包装器显式处理成功或失败,从而在操作失败时明确说明,而无需仅依赖异常。Result

这是我个人喜欢的方法。

public class Result<T>  
{
public T Value { get; }
public Exception Error { get; }
public bool IsSuccess => Error == null;

private Result(T value, Exception error)
{
Value = value;
Error = error;
}

public static Result<T> Success(T value) => new Result<T>(value, null);
public static Result<T> Failure(Exception error) => new Result<T>(default, error);
}

用法:

var result = Divide(10, 0);  
if (result.IsSuccess)
{
Console.WriteLine($"Result: {result.Value}");
}
else
{
Console.WriteLine($"Error: {result.Error.Message}");
}

此模式提供了一种结构化的方式来管理错误,而不完全依赖于异常。

编写健壮的现代 C# 代码

掌握异常处理不仅仅是避免应用程序崩溃。它更多的是关于编写可维护、清晰且有弹性的代码,以优雅地处理意外情况。通过应用这些最佳实践和现代技术(例如利用函数式编程原则、正确处理异步错误以及将异常处理集中在 ASP.NET Core 中),您不仅可以显著提高代码质量,还可以显著提高应用程序的整体稳定性。

请记住,适当的异常处理不仅可以帮助您更快地进行调试,还可以确保更好的用户体验。

如果你喜欢我的文章,请给我一个赞!谢谢


架构师老卢
资深软件架构师, 分享编程、软件设计经验, 教授前沿技术, 分享技术资源(每天发布电子书),每天进步一点点...
 最新文章