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