可空引用类型和选项模式

huy*_*itw 9 c# asp.net-core c#-8.0 nullable-reference-types

我们如何结合使用非空引用类型选项模式

假设我们有一个名为的选项模型MyOptions

需要这些选项的服务被IOptions<MyOptions> options注入到构造函数中。

配置选项的过程IServiceCollection如下:

services
    .AddOptions<MyOptions>()
    .Configure(options =>
    {
        options.Name = "ABC";
    });
Run Code Online (Sandbox Code Playgroud)

现在,问题出在MyOptions

public sealed class MyOptions
{
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

会产生警告:

CS8618不可空的属性“名称”未初始化。考虑将属性声明为可为空。

  1. 我们不想使它成为Name可空值,因为我们需要在任何地方放置传统的空值检查(这与非空值引用类型的目的相反
  2. 当方法为我们构造options实例时,我们无法创建构造函数以强制MyOptions使用非空name值创建要创建的类Configure
  3. 我们不能使用允许为null的运算符把戏(public string name { get; set; } = null!;),因为那样我们就不能确保Name设置了属性,而我们最终可能会nullName属性中出现一个无法预期的属性(在服务内部)

我还没有考虑其他选择?

Pav*_*ski 8

看来,您在这里有两种可能的选择。第一个是Options使用空字符串(而不是null值)初始化属性以避免null检查

public sealed class MyOptions
{
    public string Name { get; set; } = "";
}
Run Code Online (Sandbox Code Playgroud)

第二个是使所有属性都可以为空,并使用DisallowNull前置条件和NotNull后置条件装饰它们。

DisallowNull意味着可为空的输入参数永远不应为空,NotNull- 可为空的返回值永远不会为空。但是这些属性只影响对使用它们注释的成员的调用者的可空分析。因此,您表示您的属性永远不会返回null或设置为null,尽管声明可以为空

public sealed class MyOptions
{
    [NotNull, DisallowNull]public string? Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

和使用示例

var options = new MyOptions();
options.Name = null; //warning CS8625: Cannot convert null literal to non-nullable reference type.
options.Name = "test";
Run Code Online (Sandbox Code Playgroud)

但是下一个示例没有显示警告,因为可空分析在对象初始值设定项中还不能正常工作,请参阅Roslyn 存储库中的GitHub 问题40127

var options = new MyOptions { Name = null }; //no warning
Run Code Online (Sandbox Code Playgroud)

编辑:此问题已修复,于 2020 年 3 月在 16.5 版中发布,在将 VS 更新到最新版本后应该会消失。)

属性getter的同一张图,下面的示例没有显示任何警告,因为你指出nullable return type can't be null

var options = new MyOptions();
string test = options.Name.ToLower();
Run Code Online (Sandbox Code Playgroud)

但是尝试设置一个null值并获取它会生成一个警告(编译器足够聪明,可以检测到这种情况)

var options = new MyOptions() { Name = null };
string test = options.Name.ToLower(); //warning CS8602: Dereference of a possibly null reference.
Run Code Online (Sandbox Code Playgroud)


R. *_* V. 5

您应该选择选项 3)。在初始化过程中,不可为空的属性是否为 null 并不重要。重要的是稍后选项实例的消费者的观点。

[Required]我们可以通过使用属性注释它然后调用选项构建器来确保选项属性不会为空ValidateDataAnnotations(),例如:

public class MyOptions {
    [Required] public string MyRequiredText { get; set; } = null!;
    public string? MyOptionalText { get; set; };
}

services.AddOptions<MyOptions>()
    .Bind(Configuration.GetSection("MySettings"))
    .Configure(o => arbitrary configuration action here...)
    .ValidateDataAnnotations();

// When options are consumed from DI by `IOptions` or similar interfaces, 
// it is certain that MyRequiredText will not be null - in such case, exception will be thrown instead
Run Code Online (Sandbox Code Playgroud)

当从 DI 请求选项并且框架首先创建实例时,它会在执行所有注册的配置处理程序后验证属性上的所有属性。如果验证失败(例如,必需的属性为 null 或空字符串),则会抛出异常,这就是您应该执行的操作。

  • 刚刚尝试过这个,效果很好。通过适当的验证,意外的错误配置会显着减少。 (2认同)

Rik*_*son 4

如果属性的预期行为是它最初可能包含 null 但不应设置为 null,则尝试使用DisallowNullAttribute

#nullable enable

using System.Diagnostics.CodeAnalysis;

public sealed class MyOptions
{
    [DisallowNull]
    public string? Name { get; set; }

    public static void Test()
    {
        var options = new MyOptions();
        options.Name = null; // warning
        options.Name = "Hello"; // ok
    }

    public static void Test2()
    {
        var options = new MyOptions();
        options.Name.Substring(1); // warning on dereference
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 其他推荐阅读:https://learn.microsoft.com/en-us/dotnet/csharp/nullable-attributes (3认同)