"程序又崩了!"、"日志里全是异常但找不到原因"、"明明加了try-catch为什么还是有问题"...
如果你经常遇到这些情况,那么恭喜你,你已经踩进了C#异常处理的经典陷阱。作为一名有着10年开发经验的老程序员,我见过太多因为异常处理不当导致的线上故障。
今天这篇文章,我将用最直白的语言和最实用的代码,帮你彻底掌握C#异常处理的精髓,让你的代码从"脆弱易碎"变成"坚如磐石"。
很多开发者为了"稳定",喜欢把所有异常都捕获然后什么都不做。这就像把烟雾报警器的电池拆掉一样危险!
C#// ❌ 死亡代码 - 异常黑洞
try
{
ProcessCriticalData();
}
catch
{
// 静默处理,什么都不做
}
C#// ✅ 正确做法 - 记录日志并合理处理
try
{
ProcessCriticalData();
}
catch (SqlException ex)
{
_logger.LogError(ex, "数据库操作失败,订单ID: {OrderId}", orderId);
// 根据业务需求决定是否重新抛出
throw new BusinessException("订单处理失败,请联系客服", ex);
}
catch (Exception ex)
{
_logger.LogCritical(ex, "未知错误,需要紧急处理");
throw; // 重新抛出,让上层处理
}
⚠️ 避坑指南:
异常处理的开销是普通if判断的100倍以上!用异常控制正常业务流程会严重影响性能。
C#// ❌ 性能杀手
public User GetUserById(int id)
{
try
{
return _users.Single(u => u.Id == id);
}
catch (InvalidOperationException)
{
return null; // 用异常处理正常的"找不到"情况
}
}
C#// ✅ 性能优化版本
public User GetUserById(int id)
{
return _users.FirstOrDefault(u => u.Id == id);
}
// ✅ 更完善的版本
public class UserService
{
public Result<User> GetUserById(int id)
{
var user = _users.FirstOrDefault(u => u.Id == id);
return user != null
? Result<User>.Success(user)
: Result<User>.Failure("用户不存在");
}
}
public class Result<T>
{
public bool IsSuccess { get; private set; }
public T Data { get; private set; }
public string ErrorMessage { get; private set; }
public static Result<T> Success(T data) => new() { IsSuccess = true, Data = data };
public static Result<T> Failure(string error) => new() { IsSuccess = false, ErrorMessage = error };
}
💡 性能提升技巧:
TryParse、FirstOrDefault等方法不正确的资源管理是导致内存泄漏的主要原因,特别是在异常发生时。
C#// ❌ 资源泄漏风险
public string ReadFileContent(string fileName)
{
FileStream fs = null;
StreamReader reader = null;
try
{
fs = new FileStream(fileName, FileMode.Open);
reader = new StreamReader(fs);
return reader.ReadToEnd();
}
catch (IOException ex)
{
// 如果这里直接return,资源就泄漏了!
return string.Empty;
}
finally
{
reader?.Dispose();
fs?.Dispose();
}
}
C#// ✅ 使用using语句确保资源释放
public string ReadFileContent(string fileName)
{
try
{
using var fs = new FileStream(fileName, FileMode.Open);
using var reader = new StreamReader(fs);
return reader.ReadToEnd();
}
catch (FileNotFoundException)
{
_logger.LogWarning("文件不存在: {FileName}", fileName);
return string.Empty;
}
catch (IOException ex)
{
_logger.LogError(ex, "读取文件失败: {FileName}", fileName);
throw;
}
}
// ✅ 异步版本
public async Task<string> ReadFileContentAsync(string fileName)
{
try
{
using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true);
using var reader = new StreamReader(fs);
return await reader.ReadToEndAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "异步读取文件失败: {FileName}", fileName);
throw;
}
}
🛡️ 资源管理最佳实践:
using语句IDisposable接口的类都要考虑资源释放这是一个极其隐蔽的陷阱,finally块中的return会"吃掉"try和catch块的返回值!
C#// ❌ 隐蔽的陷阱
public string GetMessage()
{
try
{
return "来自try块的消息";
}
catch
{
return "来自catch块的消息";
}
finally
{
return "来自finally块的消息"; // 这个会覆盖前面的返回值!
}
// 结果:无论如何都返回"来自finally块的消息"
}
C#using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Appfinally
{
// 自定义业务异常
public class BusinessException : Exception
{
public BusinessException(string message) : base(message) { }
}
// API响应包装类
public class ApiResponse<T>
{
public bool Success { get; set; }
public T Data { get; set; }
public string Message { get; set; }
public static ApiResponse<T> CreateSuccess(T data)
{
return new ApiResponse<T>
{
Success = true,
Data = data,
Message = "操作成功"
};
}
public static ApiResponse<T> CreateFailure(string message)
{
return new ApiResponse<T>
{
Success = false,
Data = default(T),
Message = message
};
}
}
// 主要的服务类
public class DataService
{
private readonly ILogger<DataService> _logger;
public DataService(ILogger<DataService> logger)
{
_logger = logger;
}
public string GetMessage()
{
string result = null;
try
{
result = ProcessData();
_logger.LogInformation("数据处理成功");
}
catch (Exception ex)
{
_logger.LogError(ex, "数据处理失败");
result = "处理失败";
}
finally
{
// 只做清理工作,不要return
CleanupResources();
_logger.LogInformation("资源清理完成");
}
return result;
}
// ✅ 更优雅的方式 - 异步版本
public async Task<ApiResponse<string>> GetMessageAsync()
{
try
{
var result = await ProcessDataAsync();
return ApiResponse<string>.CreateSuccess(result);
}
catch (BusinessException ex)
{
_logger.LogWarning(ex, "业务异常");
return ApiResponse<string>.CreateFailure(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "系统异常");
return ApiResponse<string>.CreateFailure("系统繁忙,请稍后重试");
}
finally
{
await CleanupResourcesAsync();
}
}
// 模拟数据处理 - 同步版本
private string ProcessData()
{
// 模拟可能的业务异常
var random = new Random();
if (random.Next(1, 10) <= 2)
{
throw new BusinessException("用户权限不足");
}
// 模拟可能的系统异常
if (random.Next(1, 10) <= 1)
{
throw new InvalidOperationException("数据库连接失败");
}
return "数据处理成功的结果";
}
// 模拟数据处理 - 异步版本
private async Task<string> ProcessDataAsync()
{
await Task.Delay(100); // 模拟异步操作
var random = new Random();
if (random.Next(1, 10) <= 2)
{
throw new BusinessException("数据验证失败");
}
if (random.Next(1, 10) <= 1)
{
throw new InvalidOperationException("外部服务调用失败");
}
return "异步数据处理成功";
}
// 资源清理 - 同步版本
private void CleanupResources()
{
try
{
// 清理临时文件、关闭连接等
_logger.LogDebug("执行资源清理");
}
catch (Exception ex)
{
// finally块中的异常不应该影响主流程
_logger.LogWarning(ex, "资源清理时发生异常");
}
}
// 资源清理 - 异步版本
private async Task CleanupResourcesAsync()
{
try
{
await Task.Delay(50); // 模拟异步清理
_logger.LogDebug("执行异步资源清理");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "异步资源清理时发生异常");
}
}
}
// 使用示例
public class Program
{
public static async Task Main(string[] args)
{
// 配置依赖注入和日志 - 移除了HttpClient依赖
var serviceProvider = new ServiceCollection()
.AddLogging(builder => builder.AddConsole())
.AddTransient<DataService>()
.BuildServiceProvider();
var dataService = serviceProvider.GetRequiredService<DataService>();
// 测试同步方法
Console.WriteLine("=== 同步方法测试 ===");
for (int i = 0; i < 5; i++)
{
var result = dataService.GetMessage();
Console.WriteLine($"结果: {result}");
}
// 测试异步方法
Console.WriteLine("\n=== 异步方法测试 ===");
for (int i = 0; i < 5; i++)
{
var response = await dataService.GetMessageAsync();
Console.WriteLine($"成功: {response.Success}, 数据: {response.Data}, 消息: {response.Message}");
}
// 清理资源
serviceProvider.Dispose();
}
}
}

异步操作中的异常处理有特殊的规则,很多开发者容易混淆。
C#// ❌ 异步异常处理的常见错误
public async Task<string> BadAsyncMethod()
{
try
{
var task = GetDataAsync();
// 这里没有await,异常不会被捕获!
return task.Result; // 还可能导致死锁
}
catch (Exception ex)
{
// 捕获不到异常
return "error";
}
}
C#// ✅ 正确的异步异常处理
public async Task<Result<string>> GetDataSafelyAsync(CancellationToken cancellationToken = default)
{
try
{
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromSeconds(30);
var response = await httpClient.GetStringAsync("https://api.xx.com/xx", cancellationToken);
return Result<string>.Success(response);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "HTTP请求失败");
return Result<string>.Failure("网络请求失败");
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
_logger.LogWarning("请求超时");
return Result<string>.Failure("请求超时,请稍后重试");
}
catch (TaskCanceledException ex) when (cancellationToken.IsCancellationRequested)
{
_logger.LogInformation("请求被取消");
return Result<string>.Failure("请求已取消");
}
catch (Exception ex)
{
_logger.LogError(ex, "未知异常");
return Result<string>.Failure("系统异常");
}
}
catch(Exception)就像用大锤砸核桃,虽然能解决问题,但太过粗暴。
C#// ❌ 过度泛化
try
{
var user = await _userService.GetUserAsync(id);
var order = await _orderService.CreateOrderAsync(user, items);
await _emailService.SendConfirmationAsync(user.Email, order);
}
catch (Exception ex)
{
// 所有异常都一样处理,无法区分具体问题
return BadRequest("操作失败");
}
C#// ✅ 精确异常处理
public async Task<IActionResult> CreateOrder(int userId, List<OrderItem> items)
{
try
{
var user = await _userService.GetUserAsync(userId);
if (user == null)
{
return NotFound("用户不存在");
}
var order = await _orderService.CreateOrderAsync(user, items);
// 邮件发送失败不应该影响订单创建
_ = Task.Run(async () =>
{
try
{
await _emailService.SendConfirmationAsync(user.Email, order);
}
catch (Exception ex)
{
_logger.LogError(ex, "邮件发送失败,订单ID: {OrderId}", order.Id);
}
});
return Ok(order);
}
catch (ValidationException ex)
{
_logger.LogWarning(ex, "订单数据验证失败,用户ID: {UserId}", userId);
return BadRequest(ex.Message);
}
catch (InsufficientStockException ex)
{
_logger.LogWarning("库存不足,商品ID: {ProductId}", ex.ProductId);
return BadRequest($"商品 {ex.ProductName} 库存不足");
}
catch (PaymentException ex)
{
_logger.LogError(ex, "支付处理失败,用户ID: {UserId}", userId);
return BadRequest("支付失败,请检查账户余额");
}
catch (Exception ex)
{
_logger.LogError(ex, "创建订单时发生未知错误,用户ID: {UserId}", userId);
return StatusCode(500, "系统异常,请稍后重试");
}
}
异常就像犯罪现场,上下文信息就是证据,丢失了证据就很难找到真凶。
C#// ❌ 丢失异常上下文
try
{
ProcessOrder(order);
}
catch (Exception ex)
{
// 重新抛出时丢失了原始异常信息
throw new Exception("处理失败");
}
C#// ✅ 完整的异常链和上下文
public class OrderProcessor
{
private readonly ILogger<OrderProcessor> _logger;
public async Task ProcessOrderAsync(Order order)
{
var context = new ProcessingContext
{
OrderId = order.Id,
UserId = order.UserId,
StartTime = DateTime.UtcNow,
CorrelationId = Guid.NewGuid().ToString()
};
using var scope = _logger.BeginScope(new Dictionary<string, object>
{
["OrderId"] = context.OrderId,
["CorrelationId"] = context.CorrelationId
});
try
{
await ValidateOrderAsync(order, context);
await ProcessPaymentAsync(order, context);
await UpdateInventoryAsync(order, context);
await NotifyUserAsync(order, context);
}
catch (ValidationException ex)
{
throw new OrderProcessingException(
$"订单验证失败: {ex.Message}",
ex,
context);
}
catch (PaymentException ex)
{
throw new OrderProcessingException(
$"支付处理失败: {ex.Message}",
ex,
context);
}
catch (Exception ex)
{
throw new OrderProcessingException(
"订单处理过程中发生未知错误",
ex,
context);
}
}
}
// 自定义异常类,保存上下文
public class OrderProcessingException : Exception
{
public ProcessingContext Context { get; }
public OrderProcessingException(string message, Exception innerException, ProcessingContext context)
: base(message, innerException)
{
Context = context;
}
}
经过这7个陷阱的学习,我希望你记住这三个黄金法则:
💡 今日金句:
"好的异常处理不是让程序不崩溃,而是让程序在崩溃时能优雅地告诉你原因。"
🤔 思考题:
📚 延伸学习:
如果这篇文章对你有帮助,请转发给更多的C#开发同行,让我们一起写出更健壮的代码!
关注我,获取更多C#实战技巧和最佳实践!
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!