可空引用类型和选项模式

时间:2019-09-24 19:08:41

标签: c# asp.net-core c#-8.0 nullable-reference-types

如何将不可空引用类型Options pattern结合使用?

假设我们有一个名为<head>的期权模型。

需要这些选项的服务将MyOptions注入到构造函数中。

像这样在IOptions<MyOptions> options上配置选项:

IServiceCollection

现在,问题出在services .AddOptions<MyOptions>() .Configure(options => { options.Name = "ABC"; }); 的定义中:

MyOptions

会生成警告:

  

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

  1. 我们不想使public sealed class MyOptions { public string Name { get; set; } } 为可空值,因为我们需要在所有地方放置传统的空值检查(这与不可空值引用类型的目的相反)
  2. 由于Name方法为我们构造了options实例,因此我们无法创建构造函数来强制使用不可为空的MyOptions值创建的name
  3. 我们不能使用允许原谅的操作符技巧(Configure,因为那样我们不能确保设置了public string name { get; set; } = null!;属性,最后我们可以Name属性中的一个null(在服务内),这是不可预期的

我忘了考虑其他选择吗?

2 个答案:

答案 0 :(得分:7)

似乎您在这里有两个可能的选择。第一个方法是使用空字符串(而不是Options值)初始化null属性,以避免进行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更新到最新版本后应消失。)

属性获取器的图片相同,以下示例未显示任何警告,因为您指示可为空的返回类型不能为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.

答案 1 :(得分:1)

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

#include <type_traits>

template<typename T> struct Point
{
    constexpr Point() {}
    template<typename U, typename V> constexpr Point(const U& u, const V& v): x(u), y(v) {}
    T x = {}, y = {};
};

template<typename T, typename U> 
inline constexpr Point<typename std::common_type_t<T, U>> operator+(const Point<T>& p, const U& n)
{
    static_assert(std::is_same_v<std::common_type_t<T,U>, decltype(std::declval<T>() + std::declval<U>())>);
    return {p.x+n, p.y+n};
}

template<typename T, typename U>
inline constexpr Point<decltype(Point<T>{} + U{})> operator+(const Point<Point<T>>& p, const U& n)
{
    return {p.x+n, p.y+n};
}

template <class T, class U>
inline constexpr bool operator==(const Point<T> &p, const Point<U> &r)
{
    return p.x == r.x && p.y == r.y;
}

int main() {
    constexpr Point<int> p;
    constexpr Point<double> r1 = p + 1.5;
    constexpr Point<Point<int>> p2;
    constexpr Point<Point<double>> r2 = p2 + 1.5;
    constexpr Point<Point<Point<int>>> p3;
    constexpr Point<Point<Point<double>>> r3 = p3 + 1.5;
    constexpr Point<Point<Point<Point<int>>>> p4;
    constexpr Point<Point<Point<Point<double>>>> r4 = p4 + 1.5;
    static_assert(r4.x == p4.x + 1.5);
    static_assert(r4.x.x == p4.x.x + 1.5);
    static_assert(r4.x.x.x == p4.x.x.x + 1.5);
    static_assert(r4.x.x.x.x == p4.x.x.x.x + 1.5);
}