使用 C# 中的空状态静态分析属性编写更安全、更易维护的代码
处理空引用是编写可靠和可维护的 C# 应用程序的关键方面。引入可空引用类型(Nullable Reference Types)使这一点变得更加容易,允许编译器在编译时警告开发人员潜在的空引用问题。然而,在某些情况下,编译器的默认静态分析无法准确推断空值性。在这种情况下, 命名空间中的属性可以帮助填补这一空白。System.Diagnostics.CodeAnalysis
本文将深入探讨如何使用 、 和 等空状态静态分析属性来提高代码的安全性和清晰度。如果你还不熟悉 ,可以先阅读《理解 C# 中的 NotNullWhen》一文,其中详细介绍了它的用法。NotNullWhenAllowNullDoesNotReturnIfNotNullWhen
问题:编译器需要指导
考虑一个验证给定对象是否为空的方法:
public static bool ValidateObject(object? obj)
{
    return obj != null;
}
即使这个方法检查了空值,编译器仍然不知道当方法返回 时,对象一定是非空的。因此,当使用这个方法时,仍然可能出现不必要的警告:true
object? myObject = null;
if (ValidateObject(myObject))
{
    // 警告:可能的空引用。
    Console.WriteLine(myObject.ToString());
}
编译器假设最坏的情况—— 仍然可能是空的——因为方法中的空检查没有显式传达。myObject
解决方案:使用空状态静态分析属性
NotNullWhen、 和 属性允许开发人员指导编译器理解参数和返回值的空值性。AllowNullDoesNotReturnIf
- 
NotNullWhen  
NotNullWhen 属性告知编译器,根据方法的返回值,参数的空值性如何。以下是一个示例:
using System.Diagnostics.CodeAnalysis;
public static bool ValidateObject([NotNullWhen(true)] object? obj)
{
    return obj != null;
}
public static void Example1()
{
    object? myObject = GetObject();
    if (ValidateObject(myObject))
    {
        // 没有警告:编译器知道 'myObject' 不是空的。
        Console.WriteLine(myObject.ToString());
    }
}
private static object? GetObject() => null;
现在,编译器理解当 返回 时, 是保证非空的。ValidateObjecttrueobj
- 
AllowNull  
有时,你可能希望接受 作为参数,即使类型是非可空的。例如,考虑一个构造函数,当传递 时初始化对象的默认值:nullnull
using System.Diagnostics.CodeAnalysis;
public class User
{
    public string Name { get; init; }
    public User([AllowNull] string name)
    {
        Name = name ?? "Default Name";
    }
}
public static void Example2()
{
    User user = new User(null);
    // 输出:Default Name
    Console.WriteLine(user.Name);
}
AllowNull 属性告诉编译器, 可以作为输入,即使 是非可空的。nullName
- 
DoesNotReturnIf  
DoesNotReturnIf 属性用于基于条件终止应用程序或抛出异常的方法。它告知编译器,如果特定条件为 ,方法将不会返回。以下是一个示例:true
using System.Diagnostics.CodeAnalysis;
public static class Guard
{
    public static void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, object? obj)
    {
        if (isNull)
        {
            throw new ArgumentNullException(nameof(obj), "Object cannot be null.");
        }
    }
}
public static void Example3()
{
    object? myObject = null;
    // 验证对象
    Guard.ThrowIfNull(myObject == null, myObject);
    // 编译器知道 'myObject' 不是空的。
    Console.WriteLine(myObject.ToString());
}
DoesNotReturnIf 属性在参数上告诉编译器,如果 为 ,方法将不会返回。在方法调用之后,编译器假设 不是空的,从而消除警告。isNulltruemyObject
综合示例
以下是一个综合示例,结合这些属性有效地处理空状态分析:
using System;
using System.Diagnostics.CodeAnalysis;
public class Order
{
    public string Id { get; init; }
    public Order([AllowNull] string id)
    {
        Id = id ?? "Default Order ID";
    }
}
public static class Guard
{
    public static void ValidateOrder([DoesNotReturnIf(true)] bool isNull, [NotNullWhen(false)] Order? order)
    {
        if (isNull)
        {
            throw new ArgumentNullException(nameof(order), "Order cannot be null.");
        }
    }
}
public static class Program
{
    public static void Main()
    {
        Order? order = null;
        try
        {
            Guard.ValidateOrder(order == null, order);
        }
        catch (ArgumentNullException ex)
        {
            Console.WriteLine(ex.Message);
        }
        order = new Order(null);
        // 编译器知道 'order' 不是空的。
        Console.WriteLine($"Order ID: {order.Id}");
    }
}
这个示例展示了如何:
- 
AllowNull:允许在构造函数中传递 并安全地处理。null - 
DoesNotReturnIf:验证 并在条件为 时停止执行。ordertrue - 
NotNullWhen:确保编译器知道如果验证通过, 是非空的。order 
使用空状态静态分析属性的好处
- 
改进的编译器辅助:属性如 和 使编译器在空值性分析方面更加智能,减少不必要的警告。 NotNullWhenDoesNotReturnIf - 
更清晰的意图:代码明确传达了如何处理空值性,提高了可读性和可维护性。  - 
更安全的代码:通过消除运行时的空引用问题,这些属性使代码更加健壮。  
结论
NotNullWhen、 和 等空状态静态分析属性是在 C# 中使用可空引用类型不可或缺的工具。它们允许你通过填补编译器空值性分析中的空白,编写更安全、更清晰、更易维护的代码。AllowNullDoesNotReturnIf