关于C#的异常
in 默认分类 with 0 comment

前言

今天想系统记录学习一下C#的异常,以后写代码抛出异常更加规范,然后问AI列举了常用的错误类型,而且AI还说在一些性能要求高的地方尽量不要抛出异常,因为异常消耗的系统资源较多,给出了一个比较好的方案,我记录一下.

常见异常类型及适用场景

异常类型命名空间适用场景
System.ExceptionSystem所有异常的基类,通常不直接抛出
System.SystemExceptionSystem系统级异常的基类
System.ArgumentExceptionSystem向方法传递了无效参数
System.ArgumentNullExceptionSystem参数为 null,而方法不允许 null
System.ArgumentOutOfRangeExceptionSystem参数超出了允许的范围
System.NullReferenceExceptionSystem访问了 null 对象的成员
System.IndexOutOfRangeExceptionSystem数组或集合索引越界
System.InvalidOperationExceptionSystem对象当前状态不支持该操作
System.IO.IOExceptionSystem.IO文件或流操作出错
System.DivideByZeroExceptionSystem除数为 0 时抛出
System.FormatExceptionSystem字符串格式不正确,比如无法转换为数字
System.OverflowExceptionSystem算术运算溢出(如超出 int 范围)
System.NotImplementedExceptionSystem某个方法尚未实现(通常用于占位)
System.NotSupportedExceptionSystem某方法在当前环境不受支持
System.TimeoutExceptionSystem操作超时(例如网络、串口、线程)
System.UnauthorizedAccessExceptionSystem无权限访问资源(如文件、注册表)

自定义异常类型

当系统提供的异常类型不够用时,可以自定义异常:

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

用法:

throw new MyCustomException("出现自定义异常");

异常处理最佳实践

带有错误返回的方法

public readonly struct Result
{
    public bool Success { get; }
    private readonly string? _message;
    public string? Message => Success ? null : _message;

    private Result(bool success, string? message)
    {
        Success = success;
        _message = message;
    }

    public static Result Ok() => new Result(true, null);
    public static Result Ok(string message) => new Result(true, message);
    public static Result Fail(string message) => new Result(false, message);
}

public readonly struct Result<T>
{
    public bool Success { get; }
    private readonly string? _message;
    public string? Message => Success ? null : _message;
    public T Data { get; }

    private Result(bool success, T data, string? message)
    {
        Success = success;
        Data = data;
        _message = message;
    }

    public static Result<T> Ok(T data) => new Result<T>(true, data, null);
    public static Result<T> Ok(T data, string message) => new Result<T>(true, data, message);
    public static Result<T> Fail(string message) => new Result<T>(false, default!, message);
}

使用方法

// 1. 无泛型版本
Result r1 = Result.Ok();
Console.WriteLine(r1.Success); // True
Console.WriteLine(r1.Message ?? "无消息"); // 无消息

Result r2 = Result.Fail("操作失败");
Console.WriteLine(r2.Success); // False
Console.WriteLine(r2.Message); // 操作失败

// 2. 泛型版本成功返回数据
Result<int> r3 = Result<int>.Ok(123);
if (r3.Success)
{
    Console.WriteLine($"成功,数据是 {r3.Data}"); // 成功,数据是 123
}

// 3. 泛型版本失败
Result<string> r4 = Result<string>.Fail("找不到数据");
Console.WriteLine(r4.Success); // False
Console.WriteLine(r4.Message); // 找不到数据

// 4. 泛型版本成功带消息
Result<string> r5 = Result<string>.Ok("hello world", "附加消息");
Console.WriteLine(r5.Success); // True
Console.WriteLine(r5.Message); // 附加消息
Console.WriteLine(r5.Data);    // hello world

我的建议

对于一些用户的输入等可以提示的消息使用 Result ,对于捕获到的错误,在提示用户的同时,也写入日志之中,这样更有助于规范的代码.