![](/img/trans.png)
[英]Why do we get possible dereference null reference warning, when null reference does not seem to be possible?
[英]Why don't I get a warning about possible dereference of a null in C# 8 with a class member of a struct?
在启用了可为空引用类型的 C# 8 项目中,我有以下代码,我认为它应该给我一个关于可能的 null 取消引用的警告,但没有:
public class ExampleClassMember
{
public int Value { get; }
}
public struct ExampleStruct
{
public ExampleClassMember Member { get; }
}
public class Program
{
public static void Main(string[] args)
{
var instance = new ExampleStruct();
Console.WriteLine(instance.Member.Value); // expected warning here about possible null dereference
}
}
当使用默认构造函数初始化instance
时, instance.Member
被设置为ExampleClassMember
的默认值,即null
。 因此, instance.Member.Value
将在运行时抛出NullReferenceException
。 据我了解 C# 8 的可空性检测,我应该得到一个关于这种可能性的编译器警告,但我没有; 这是为什么?
请注意,没有理由对Console.WriteLine()
的调用发出警告。 引用类型属性不是可为空的类型,因此编译器无需警告它可能是 null。
您可能会争辩说编译器应该警告struct
本身中的引用。 这对我来说似乎是合理的。 但是,事实并非如此。 这似乎是一个漏洞,由值类型的默认初始化引起,即必须始终有一个默认(无参数)构造函数,它总是将所有字段清零(引用类型字段为空,数字类型为零等。 )。
我称之为漏洞,因为理论上不可为空的引用值实际上应该始终为非空。 杜::)
这个漏洞似乎在这篇博客文章中得到解决: 在 C# 中引入可空引用类型
避免空值 到目前为止,警告都是关于保护可空引用中的空值不被取消引用。 硬币的另一面是避免在不可为空的引用中出现空值。
null 值可以通过多种方式产生,其中大多数值得警告,而其中一些会导致另一个最好避免的“警告海洋”:
…
- 使用具有不可为空引用类型的字段的结构的默认构造函数。 这个是偷偷摸摸的,因为默认构造函数(将结构清零)甚至可以在许多地方隐式使用。 最好不要警告[emphasis mine - PD] ,否则许多现有的结构类型将变得无用。
换句话说,是的,这是一个漏洞,但不,这不是错误。 语言设计者意识到了这一点,但选择将这种情况排除在警告之外,因为考虑到struct
初始化的工作方式,否则这样做是不切实际的。
请注意,这也符合该功能背后的更广泛理念。 来自同一篇文章:
所以我们希望它抱怨你现有的代码。 但并不讨厌。 以下是我们将如何尝试达到这种平衡:
…
- 即使您对所有警告做出反应并消除了所有警告,也无法保证 null 安全[强调我的 - PD] 。 分析中有很多漏洞是必然的,也有一些是选择的。
最后一点:有时警告是“正确”的事情,但会一直在现有代码上触发,即使它实际上是以 null 安全方式编写的。 在这种情况下,我们会为了方便而不是正确而犯错。 我们不能在现有代码上产生“警告海洋”:太多的人只会关闭警告而永远不会从中受益。
另请注意,名义上不可为空的引用类型(例如string[]
)的 arrays 也存在同样的问题。 创建数组时,所有参考值都是null
,但这是合法的,不会产生任何警告。
这么多解释为什么事情是这样的。 那么问题就变成了,该怎么办呢? 这要主观得多,我认为没有正确或错误的答案。 那就是说……
我个人会根据具体情况处理我的struct
类型。 对于那些意图实际上是可为空的引用类型的人,我会应用?
注解。 否则,我不会。
从技术上讲, struct
中的每个引用值都应该是“可空的”,即包括?
具有类型名称的可空注释。 但与许多类似的特性(如 C# 中的 async/await 或 C++ 中的const
)一样,这具有“感染性”方面,因为您需要稍后覆盖该注释(使用!
注释),或包含显式null 检查,或者只将该值分配给另一个可为空的引用类型变量。
对我来说,这违背了启用可为空引用类型的许多目的。 由于此类struct
类型的成员无论如何都需要特殊情况处理,并且由于在仍然能够使用不可空引用类型的同时真正安全地处理它的唯一方法是在您使用struct
的任何地方进行 null 检查,我觉得这是一个合理的实现选择,接受当代码初始化struct
时,代码有责任正确地这样做并确保不可为空的引用类型成员实际上被初始化为非空值。
这可以通过提供一种“官方”的初始化方法来帮助,例如非默认构造函数(即带有参数的构造函数)或工厂方法。 仍然存在使用默认构造函数或根本不使用构造函数的风险(如在数组分配中),但通过提供一种方便的方法来正确初始化struct
,这将鼓励使用它的代码避免 null 在非- 可空变量。
也就是说,如果您想要的是 100% 安全的可空引用类型,那么显然该特定目标的正确方法是始终使用?
注释struct
中的每个引用类型成员。 . 这意味着每个字段和每个自动实现的属性,以及直接返回此类值或此类值的乘积的任何方法或属性获取器。 然后,消费代码将需要在将此类值复制到不可为空的变量中的每个点包含 null 检查或容空运算符。
鉴于@peter-duniho 的出色回答,似乎从 2019 年 10 月开始,最好将所有非值类型成员标记为可为空的引用。
#nullable enable
public class C
{
public int P1 { get; }
}
public struct S
{
public C? Member { get; } // Reluctantly mark as nullable reference because
// https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/
// states:
// "Using the default constructor of a struct that has a
// field of nonnullable reference type. This one is
// sneaky, since the default constructor (which zeroes
// out the struct) can even be implicitly used in many
// places. Probably better not to warn, or else many
// existing struct types would be rendered useless."
}
public class Program
{
public static void Main()
{
var instance = new S();
Console.WriteLine(instance.Member.P1); // Warning
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.