可空引用类型和选项模式
Posted
技术标签:
【中文标题】可空引用类型和选项模式【英文标题】:Nullable Reference Types and the Options Pattern 【发布时间】:2020-01-24 23:03:01 【问题描述】:我们如何将不可为空的引用类型与Options pattern结合使用?
假设我们有一个名为 MyOptions
的选项模型。
需要这些选项的服务将IOptions<MyOptions> options
注入到构造函数中。
在IServiceCollection
上配置选项,如下所示:
services
.AddOptions<MyOptions>()
.Configure(options =>
options.Name = "ABC";
);
现在,问题在于MyOptions
的定义:
public sealed class MyOptions
public string Name get; set;
产生警告:
CS8618 不可为空的属性“名称”未初始化。考虑将属性声明为可为空。
-
我们不想让
Name
可以为空,因为我们需要在任何地方进行传统的空检查(这与不可为空的引用类型的目的背道而驰)
我们无法创建构造函数来强制使用不可为空的name
值创建MyOptions
类,因为Configure
方法为我们构造了选项实例
我们不能使用 null-forgiving 运算符 技巧 (public string name get; set; = null!;
),因为这样我们就不能确保设置了 Name
属性,我们最终会得到一个 null
在Name
属性中,这不是预期的(在服务内)
还有其他我忘记考虑的选项吗?
【问题讨论】:
string.Empty
是否适合您的用例?我假设您对填充值的检查是使用string.IsNullOrWhiteSpace
。
您检查过 IValidateOptions.Validate()
调用链接到可能满足我们需求的选项注册方法。谢谢。
是的,我同意。我认为他们试图与现有的“可空值类型”功能保持一致,但恕我直言,与拥有一个更准确地描述该功能实际功能的名称相比,这不是一个重要的目标。 :)
@huysentruitw 问题不是关于模式,而是关于它的初始化机制,这与任何反序列化器没有什么不同。配置有同样的问题——两种机制都使用基于属性的初始化,因此会产生可空性错误。 两个都必须使用基于构造函数的初始化来避免该问题。
【参考方案1】:
看来,您在这里有两种可能的选择。第一个是使用空字符串(而不是null
值)初始化Options
属性以避免null
检查
public sealed class MyOptions
public string Name get; set; = "";
第二个是使所有属性都可以为空,并使用DisallowNull
前置条件和NotNull
后置条件来装饰它们。
DisallowNull
表示可空输入参数永远不应为空,NotNull
- 可空返回值永远不会为空。
但是这些属性只影响使用它们注释的成员的调用者的可空分析。因此,您表示您的属性永远不能返回null
或设置为null
,尽管声明可以为空
public sealed class MyOptions
[NotNull, DisallowNull]public string? Name get; set;
以及使用示例
var options = new MyOptions();
options.Name = null; //warning CS8625: Cannot convert null literal to non-nullable reference type.
options.Name = "test";
但是下一个示例没有显示警告,因为可空分析在对象初始化器中还不能正常工作,请参阅 Roslyn 存储库中的 GitHub 问题 40127。
var options = new MyOptions Name = null ; //no warning
(编辑:此问题已修复,于 2020 年 3 月在 16.5 版中发布,在将 VS 更新到最新版本后应该会消失。)
属性getter的同一张图,下面的示例没有显示任何警告,因为您指出可以为空的返回类型不能是null
var options = new MyOptions();
string test = options.Name.ToLower();
但尝试设置 null
值并获取它会生成警告(编译器足够聪明,可以检测到此类情况)
var options = new MyOptions() Name = null ;
string test = options.Name.ToLower(); //warning CS8602: Dereference of a possibly null reference.
【讨论】:
【参考方案2】:如果该属性的预期行为是它最初可能包含 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
【讨论】:
补充推荐阅读:docs.microsoft.com/en-us/dotnet/csharp/nullable-attributes【参考方案3】:您应该选择选项 3)。初始化过程中不可为空的属性是否为空都没有关系。重要的是稍后选项实例的使用者的观点。
我们可以通过使用[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
当从 DI 请求选项并且框架首先创建实例时,它会在执行所有注册的配置处理程序后验证属性上的所有属性。如果验证失败(例如 required 属性为 null 或空字符串),则抛出异常,这是您应该追求的。
【讨论】:
以上是关于可空引用类型和选项模式的主要内容,如果未能解决你的问题,请参考以下文章