如何将不可空引用类型与Options pattern结合使用?
假设我们有一个名为<head>
的期权模型。
需要这些选项的服务将MyOptions
注入到构造函数中。
像这样在IOptions<MyOptions> options
上配置选项:
IServiceCollection
现在,问题出在services
.AddOptions<MyOptions>()
.Configure(options =>
{
options.Name = "ABC";
});
的定义中:
MyOptions
会生成警告:
CS8618不可初始化的属性“名称”未初始化。考虑将属性声明为可为空。
public sealed class MyOptions
{
public string Name { get; set; }
}
为可空值,因为我们需要在所有地方放置传统的空值检查(这与不可空值引用类型的目的相反)Name
方法为我们构造了options实例,因此我们无法创建构造函数来强制使用不可为空的MyOptions
值创建的name
类Configure
,因为那样我们不能确保设置了public string name { get; set; } = null!;
属性,最后我们可以Name
属性中的一个null
(在服务内),这是不可预期的我忘了考虑其他选择吗?
答案 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);
}