最近 VS2019 正式版发布了, 装下来顺便试用了一下 C#8.0, 最大的看点应该就是可空引用类型了. 不过 C#8.0 仍然处于 Beta 的状态, 而且试用时也遇到了几个坑.
背景知识说明:
所谓的可空引用类型是指, 一旦启用了可空引用类型这个新特征, 引用类型将默认被视为不可空, 无法赋予 null, 除非手工将它设为可空引用类型.
实战示例:
首先是新建一个 C# 的项目, 在 项目文件 (.csproj) 里加入两行配置, 目的是启用 "C#8.0 语言" 和 "可空引用类型":
- <LangVersion>
- 8.0
- </LangVersion>
- <NullableContextOptions>
- enable
- </NullableContextOptions>
整个文件看起来是这样的:
这样就算是整个项目全局启用了可空引用类型了.
注意:
在 VS2019 正式版中, 使用
<NullableContextOptions>enable</NullableContextOptions>
而不是使用
<NullableReferenceTypes>true</NullableReferenceTypes>
后者在正式版中已经失效了.
如果不希望全局启用可空引用类型的话, 可以在程序代码中加入以下编译指令:
#nullable enable
这样可以在加入了该指令的文件中, 单独启用可空引用类型. 但是, 极度不推荐这种做法. 为什么呢? 因为该指令仅仅在该文件中有效, 不能跨文件生效, 从而无法阻止 null 逃逸到使用了该指令的文件中, 也就是说, 用了也等于没用.
一个很简单的例子足以证明:
注意上面项目文件中并没有全局启用可空引用类型, 而下面的 Class1.cs 中使用了编译器指令来单独启用可空引用类型.
从运行结果可见, 空引用仍然逃逸到使用了该指令的作用域中了. 别说编译错误, 连编译警告都没有. 完全达不到理想的效果.
因此, 强烈建议在项目文件中全局启用可空引用类型, 而不是在某个源文件中单独使用.
另外, 还有一点要注意的是, 即使启用了可空引用类型后, 默认情况下, 即使对不可空引用赋予 null, 编译器也只会生成编译警告, 而不是编译错误. 仍然是能够编译通过的. 一个大项目中, 编译警告不可避免, 甚至可能会很多, 从而淹没了 "给不可空引用类型赋予空值" 这种不起眼的警告.
因此, 建议将特定的警告视为错误. 警告编号为 8600,8625,8618,8604, 或者将所有警告视为错误. 具体是在项目文件中加入以下设置(见图一):
<WarningsAsErrors>8600 8625 8618 8604</WarningsAsErrors>
或者在项目编辑器中设置也可以:
这是我自己测试得出的结果, 可能还有其它潜在的相关警告编号我没有测试出来. 如果有谁知道的话, 告诉我一下, 谢谢.
做好这些配置之后, 可以看到引用类型默认都不能赋予空值了:
这时候普通的引用类型的变量和参数都不能设为 null 了.
这样可以防止空值扩散开来, 引起恼人的空引用异常.
但是, 这里有个坑需要注意!!!!
struct 里不适用可空引用的规则!!
struct 里不适用可空引用的规则!!
struct 里不适用可空引用的规则!!
这种情况下直接运行, 仍然会抛出空引用异常!!!C#8.0 仍未能完全封堵住空引用的逃逸.
其实我还是比较赞同用不可空引用类型的方案的, 而不是可空引用类型的方案. 毕竟我想要的, 只不过是一个不可空的断言, 只是想利用不可空引用来划分安全边界, 从而防止空值的扩散. 简单来说就是想将变量和参数明确声明为不可空引用类型. 因为历史和现实的原因, 大量的库都还没能全面使用可空引用类型. 这种情况下, 只有我使用可空引用类型, 是不靠谱的. 无法划分安全边界.
然而 C# 选择了可空引用类型的方案, 而且还不是强制启用, 而且默认只是警告.. 跟没有一样...
比方说, 我使用了一个第三方库项目, 而空值的来源是正好是该库项目的, 而我对这个库并没有源代码或者修改权限. 这时候就无法阻止空值逃逸到我的项目中了.
还是之前的代码, 只是稍微做一下修改. 新增了一个库项目 ClassLibrary1, 这个库并没有使用可空引用类型.
库的代码如下:
很简单, 就是 LibClass3.GetInstance()本应该返回 LibClass2 的实例, 但是出于某种原因, 返回了 null. 但是我的项目中使用了 LibClass2 和 LibClass3. 我的项目是全局启用了可空引用类型的:
编译正常, 毫无警告和错误. 一旦运行, 则抛出空引用异常:
可见, 目前来说, C#8.0 的可空引用类型并不能解决外源性的空值扩散, 只能解决内源性的空值扩散, 无法跨模块生效. 还是洗洗睡吧.
参考资料:
https://www.youtube.com/watch?v=VdC0aoa7ung
来源: https://www.cnblogs.com/zlmdy/p/10656793.html