如何检测一个类型在运行时是否可以为空?

How to detect whether a type can be nullable at runtime?

我正在尝试检测一个类型在运行时是否可以为空,以将该类型转换为相应的 GraphQL 类型,例如:

可空引用类型启用:

可空引用类型禁用:

我在调整检测到类型可为空性的代码时遇到问题:

bool isNullable = !typeInfo.IsValueType;

我如何更改它以便它与启用和禁用的可为空引用类型一起工作?

请注意,有一些很好的方法可以检查 "the old" 适用于 Stack Overflow 上详细记录的值类型的可空类型。

然后我将只关注可空 reference 类型,并提供检查其中一个是否有效的方法。

让我先总结一下我对这个问题的评论,因为它们相当重要。

与新功能的名称相反,可空引用 types 不是关于 types,而是关于 things这些类型是用来干什么的。这些东西是:

  • 字段
  • 属性
  • 方法return值
  • 方法参数

现在,这当然也适用于局部变量,但您需要一种完整的“另一种内省”来处理解码指令。我不知道这种信息是如何编码在局部变量的实际指令中的。

好吧,让我们看看一些代码(顺便说一句,我使用 LINQPad 和 Roslyn 实验模式来测试所有这些):

public string? Nullable;
public string NonNullable;

这是两个 public 字段。忽略这是否是个好主意。您将如何检查这些字段的类型并检测是否存在此问号?

好吧,让我们试试简单的路线:

Type nullable = GetType().GetField("Nullable").FieldType;
Type nonNullable = GetType().GetField("NonNullable").FieldType;
Console.WriteLine(ReferenceEquals(nullable, nonNullable));

运行 这给了我:

True

很明显这是行不通的。 Type 对象是完全相同的 实例 。他们不只是比较相等,我得到了同样的东西,没有区别。基本上,FieldType 忽略了这个问号的存在与否。

我上面的评论有一些细节,但主要原因是所有现有的 nuget 包和编译的代码仍然可以使用这个新的支持。不需要重写任何代码来突然处理像 NullableReferenceType<T> 这样的事情。 这是一件好事,但也意味着您仍将传递 null-references 并从现有的 nuget 包中取回它们。

好的,那么,我们如何检测呢?答案是关于可空性的信息没有附加到类型,正如我上面提到的,而是附加到具有类型的 thing,在本例中是字段。

让我们显示这些字段的属性(我再次使用 LINQPad):

GetType().GetField("Nullable").GetCustomAttributes().Dump();
GetType().GetField("NonNullable").GetCustomAttributes().Dump();

这给出了这个输出:

正如您在此处看到的,Nullable 字段有一个附加属性 NullableAttribute。我必须承认,我不知道其他属性是什么,我将不得不进行更多调查。

这个 NullableAttribute 属性比这个简单示例显示的要复杂得多,因为它有一个集合 属性 和 bool 个值。让我们看一个稍微复杂一点的例子:

public List<string>? Nullable1;
public List<string?>? Nullable2;

这里,两个字段都是对列表的可为空引用,不同之处在于我说过其中一个列表包含对字符串的可为空引用,而另一个不包含。

以下是对这些集合的一些思考:

GetType().GetField("Nullable1").GetCustomAttributesData().Dump();
GetType().GetField("Nullable2").GetCustomAttributesData().Dump();

及其输出:

在这里您可以看到此集合中的 second 元素有所不同(我 "circled" 它们带有红色...矩形... ),我希望第一个元素应用于列表,第二个元素应用于第一个泛型类型参数。如果您有包含泛型类型的泛型列表,参数的数量将相应增加。

您还可以找到关于此的更多信息excellent blog post by Rico Suter