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

4

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

启用可空引用类型:

  • string 转换为 String!
  • string? 转换为 String

禁用可空引用类型:

  • string 转换为 String
  • NonNull<string> 转换为 String! (NonNull 是自定义库类型)

我遇到了检测类型可空性的代码适配问题:

bool isNullable = !typeInfo.IsValueType;

我该如何更改它,使其能够在启用和禁用可空引用类型的情况下运行?


您的意思是检查字段、属性、方法返回值或方法参数是否可为空,包括对可空引用类型的支持? - Lasse V. Karlsen
我之所以问是因为与新支持的名称相反,这并不是关于类型,而更多地关于有该类型的事物。换句话说,这个新支持附加到属性(例如),而不是属性的类型。 - Lasse V. Karlsen
@LasseVågsætherKarlsen 我很困惑。为什么他们会选择那个? - Shoe
新的支持是使用属性完成的,它们附加到成员或参数上,而不是这些类型,因此如果您找到一个使用FieldInfo的字段,然后检查FieldInfo.FieldType,那么该Type对象将不知道该字段已被配置为非空。 - Lasse V. Karlsen
1
我似乎找不到任何东西,一边在这里写评论一边尝试了几次。让我先写下答案的开头,然后我们就能看看我会出现多少问题 :) - Lasse V. Karlsen
显示剩余7条评论
1个回答

5
请注意,在Stack Overflow上有很好的方法可以检查适用于值类型的“旧”可空类型,这些方法已经有详细的文档记录。
我将只关注可空的引用类型,并提供检查是否存在这些类型的方法。
首先,让我总结一下对问题的评论,因为它们非常重要。
与新功能的名称相反,可空引用类型并不是关于类型的,而是关于这些类型用于的事物。这些事物包括:
- 字段 - 属性 - 方法返回值 - 方法参数
当然,这也适用于局部变量,但您需要另一种内省方式来处理解码指令。我不知道这种信息是否编码在局部变量的实际指令中。
好的,现在,让我们看一些代码(顺便说一下,我正在使用带有Roslyn实验模式的LINQPad来测试所有这些):
public string? Nullable;
public string NonNullable;

这是两个公共字段。忽略是否是一个好主意。你如何检查这些字段的类型并检测是否存在或缺少这个问号?
好吧,让我们尝试简单的方法:
Type nullable = GetType().GetField("Nullable").FieldType;
Type nonNullable = GetType().GetField("NonNullable").FieldType;
Console.WriteLine(ReferenceEquals(nullable, nonNullable));

运行此代码会给我以下结果:
True

显然,这行不通。 Type 对象是完全相同的实例。它们不仅比较相等,我得到了相同的东西,没有任何区别。基本上,FieldType 忽略了这个问号的存在或缺失。
上面我的评论中有一些细节,但这样做的主要原因是由于现有的 nuget 包和已编译的代码仍将与此新支持一起工作。因此,没有必要重写任何代码来处理突然出现的 NullableReferenceType<T> 等内容。 这是一件好事,但也意味着您仍将传递空引用并从现有的 nuget 包中获取它们。
那么,我们如何检测此问题?答案是,关于可空性的信息未附加到类型上,正如我上面提到的那样,而是附加到具有该类型的对象,在此情况下为字段。
让我们在这些字段上显示属性(再次使用 LINQPad):
GetType().GetField("Nullable").GetCustomAttributes().Dump();
GetType().GetField("NonNullable").GetCustomAttributes().Dump();

这将产生以下输出:

Nullable fields attributes

正如您在这里看到的那样,Nullable字段有一个额外的属性,NullableAttribute。我必须承认,我不知道那个其他属性是关于什么的,我需要进一步调查。

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

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

这里,两个字段都是指向列表的可空引用,不同之处在于我说其中一个列表包含了可空字符串引用,而另一个则没有。

以下是一些反射来查看这些集合:

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

和它们的输出:

nullable generic types

在这里,您可以看到此集合中的第个元素存在差异(我用红色...矩形...标出),我希望第一个元素适用于列表,第二个元素适用于第一个通用类型参数。如果您有包含通用类型的通用列表,则参数数量将相应增加。

您还可以在 Rico Suter 的博客文章中找到更多信息。


这也可能会有所帮助:https://blog.rsuter.com/the-output-of-nullable-reference-types-and-how-to-reflect-it/ - Shoe
@Shoe 很好,我记不得那篇博客文章的链接或名称了,但这是我学习它时阅读的文章之一。我已经在我的答案中加入了提及。 - Lasse V. Karlsen
一位微软员工(@333fred)向我提供了 https://github.com/dotnet/corefx/issues/38087 的链接,以跟踪实现反射机制来解决原始问题。 - Shoe
由于问题的评论可能不会永久存在,因此将您的答案完全独立化可以改善它。 - Ian Ringrose
@IanRingrose,你能告诉我你的想法吗?你是指我应该去掉对LINQPad的使用吗?不确定你所说的“完全独立”具体是什么意思。请给予建议。此外,最近因解雇版主而引起的骚动后,我正在冷静下来休息。如果在此期间没有更多的问题出现,我将在12月回来。如果你有改进的想法,请随意编辑我的答案,甚至可以大部分借鉴并制作自己的答案。 - Lasse V. Karlsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接