为什么不';我没有得到一个关于C#8中null可能与结构的类成员解除引用的警告吗?
在启用了的C#8项目中,我有以下代码,我认为这些代码应该给我一个关于可能的空解引用的警告,但没有:为什么不';我没有得到一个关于C#8中null可能与结构的类成员解除引用的警告吗?,c#,nullable,c#-8.0,nullable-reference-types,C#,Nullable,C# 8.0,Nullable Reference Types,在启用了的C#8项目中,我有以下代码,我认为这些代码应该给我一个关于可能的空解引用的警告,但没有: 公共类示例类成员 { 公共int值{get;} } 公共结构示例结构 { 公共示例类成员成员{get;} } 公共课程 { 公共静态void Main(字符串[]args) { var instance=newexamplestuct(); Console.WriteLine(instance.Member.Value);//此处预期会出现关于可能的空解引用的警告 } } 当使用默认构造函数初始
公共类示例类成员
{
公共int值{get;}
}
公共结构示例结构
{
公共示例类成员成员{get;}
}
公共课程
{
公共静态void Main(字符串[]args)
{
var instance=newexamplestuct();
Console.WriteLine(instance.Member.Value);//此处预期会出现关于可能的空解引用的警告
}
}
当使用默认构造函数初始化
instance
时,instance.Member
被设置为ExampleClassMember
的默认值,即null
。因此,instance.Member.Value
将在运行时抛出NullReferenceException
。据我所知,C#8的可空性检测,我应该得到关于这种可能性的编译器警告,但我没有;这是为什么?请注意,调用控制台.WriteLine()
时没有理由发出警告。引用类型属性不是可为null的类型,因此编译器不需要警告它可能为null
您可能认为编译器应该警告struct
本身中的引用。这对我来说似乎是合理的。但事实并非如此。这似乎是一个漏洞,由值类型的默认初始化引起,即必须始终有一个默认(无参数)构造函数,它总是将所有字段置零(引用类型字段为null,数字类型为零,等等)
我称之为漏洞,因为理论上不可为null的引用值实际上应该始终为非null!Duh.)
这篇博客文章似乎解决了这个漏洞:
避免空值
到目前为止,警告是关于保护可空引用中的空值不被取消引用。硬币的另一面是避免在不可空的引用中出现空值
空值存在的方式有两种,其中大多数值得注意,而其中的两种会导致另一个“警告之海”,最好避免:
- 使用具有不可空引用类型字段的结构的默认构造函数。这是一个秘密,因为默认构造函数(将结构置零)甚至可以在许多地方隐式使用最好不要发出警告[emphasis mine-PD],否则许多现有结构类型将变得无用
struct
初始化的工作方式,否则将不切实际
请注意,这也符合该功能背后更广泛的理念。同一条:
因此,我们希望它抱怨您现有的代码。但并不令人讨厌。以下是我们将如何努力实现这一平衡:
string[]
)的数组,也存在同样的问题。创建数组时,所有参考值均为null
,但这是合法的,不会生成任何警告
这就是为什么事情是这样的原因。然后问题就变成了,该怎么办?这是非常主观的,我不认为有一个正确或错误的答案。也就是说 我个人会根据具体情况处理我的
struct
类型。对于那些意图实际上是可空引用类型的对象,我将应用?
注释。否则,我不会
从技术上讲,struct
中的每一个引用值都应该是“可空的”,也就是说,在类型名称中包含?
可空的注释。但与许多类似的特性(如C#中的async/await或C++中的const
)一样,这有一个“传染性”方面,即您以后需要重写该注释(使用!
注释),或者包括显式的空检查,或者只将该值分配给另一个可为空的引用类型变量
对我来说,这破坏了启用可空引用类型的许多目的。由于struct
类型的此类成员无论如何在某个时候都需要特殊的大小写处理,而且在仍然能够使用不可为空的引用类型的情况下,真正安全地处理它的唯一方法是在使用struct
的任何地方进行空检查,我认为,当代码初始化结构时,代码有责任正确地进行初始化,并确保不可为null的引用类型成员实际上已初始化为非null值,这是一个合理的实现选择
这可以通过提供一种“官方”的初始化方法来辅助,例如非默认构造函数(即带有参数的构造函数)或工厂方法。使用默认构造函数或根本不使用构造函数(如在数组分配中)仍然存在风险,但是通过提供c
#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
}
}