不可空引用类型的默认值VS不可空引用类型的默认值

如何解决不可空引用类型的默认值VS不可空引用类型的默认值

这不是我关于可空引用类型的第一个问题,因为我已经经历了几个月。但是,我经历的越多,我就越困惑,并且看到该功能所带来的价值也就越少。

以该代码为例

string? nullableString = default(string?);
string nonNullableString = default(string);

int? nullableInt = default(int?);
int nonNullableInt = default(int);

执行该操作会得到:

nullableString => null

nonNullableString => null

nullableInt => null

nonNullableInt => 0

(不可为空)整数的默认值始终为0,但 对我来说,不可为空的字符串的默认值为null没有意义。 为什么选择这个?这与我们一直习惯的不可为空的原则相反。 我认为默认的不可为空的字符串的默认值应该是String.Empty

我的意思是必须在C#实现的某个深处指定0int的默认值。我们也可以选择12,但没有,共识是0。因此,当可空引用类型功能被激活时,我们不能仅将string的默认值指定为String.Empty吗?此外,微软似乎希望在不久的将来通过.NET 5项目默认激活它,因此该功能将成为 normal 行为。

现在使用对象的相同示例:

Foo? nullableFoo = default(Foo?);
Foo nonNullableFoo = default(Foo);

这给出了:

nullableFoo => null

nonNullableFoo => null

同样,这对我来说没有意义,我认为Foo的默认值应为new Foo()(如果没有可用的无参数构造函数,则会产生编译错误)。 为什么默认情况下将不应该为null的对象设置为null?

现在进一步扩展这个问题

string nonNullableString = null;
int nonNullableInt = null;

编译器会针对第一行发出警告,如果在我们的.csproj文件<WarningsAsErrors>CS8600</WarningsAsErrors>中进行简单配置,则该行可能会转换为错误。 并按预期给出了第二行的编译错误。

因此,不可为空的值类型与不可为空的引用类型之间的行为是不相同的,但这是可以接受的,因为我可以重写它。

但是这样做的时候:

string nonNullableString = null!;
int nonNullableInt = null!;

第一行编译器就可以了,完全没有警告。 我最近在体验可为空的引用类型功能时发现了null!,并且我希望编译器也能适用于第二行,但事实并非如此。现在,我对于微软为何决定实施不同的行为感到非常困惑。

考虑到它不能完全防止将null包含在不可为空的引用类型变量中,似乎此新功能不会改变任何东西,也根本不会改善开发人员的生活(相对不能为空的非空值类型,因此不需要进行空检查)

因此,最后似乎唯一增加的价值仅仅是签名。现在,开发人员可以清楚地知道方法的返回值是否可以为null或属性是否可以为null(例如,在数据库表的C#表示中,其中NULL是列中的允许值) )。

除了我看不到如何有效使用此新功能外,能否请您给我其他有关如何使用可为空的引用类型的有用示例? 我真的很想充分利用此功能来改善开发人员的生活,但我真的不知道如何...

谢谢

解决方法

再次对我来说这没有意义,我认为Foo的默认值应该是new Foo()(如果没有可用的无参数构造函数,则会给出编译错误)

这是一种意见,但是:这不是实现方式。对于引用类型,default的意思是null,即使根据可空性规则它无效。编译器会在Foo nonNullableFoo = default(Foo);行中发现并警告您:

警告CS8600将空文字或可能的空值转换为不可空类型。

关于string nonNullableString = null!;

第一行编译器就可以了,完全没有警告。

告知以忽略它;这就是! 的意思。如果您告诉编译器不要抱怨某些事情,那么抱怨它没有抱怨是无效的。

因此,最后似乎唯一增加的价值仅仅是签名。

否,它的有效性更高,但是如果您忽略它发出的警告(上面的CS8600),并且您抑制它所做的其他事情,对您来说(!:是的,它将没有太大用处。所以...不这样做吗?

,

您对编程语言设计的工作方式感到困惑。

默认值

(不可为空的)整数的默认值始终为0,但对我来说,不可为空的字符串的默认值为null没有意义。为什么选择这个?这完全违反了我们一直习惯的非空原则。我认为默认的不可为空的string的默认值应该为String.Empty

变量的默认值从一开始就是C#语言的基本功能。 The specification defines the default values

对于 value_type 的变量,默认值与value_type的默认构造函数(请参阅默认构造函数)计算的值相同。 对于 reference_type 的变量,默认值为null

从实际的角度来看,这是有道理的,因为默认值的基本用法之一是在声明给定类型的值的新数组时。通过此定义,运行时可以将分配的数组中的所有位全部置零-值类型的默认构造函数在所有字段中始终为全零值,并且null表示为全零引用。从字面上看,这是规范的下一行:

初始化默认值通常是通过让内存管理器或垃圾收集器在分配内存之前将内存初始化为全零位来完成的。因此,使用全零位表示空引用很方便。

现在可空引用类型(NRT)随C#8一起发布。这里的选择不是“尽管使用NRT还是将默认值实现为null”,而是“不要浪费时间和资源来尝试完全改写default关键字的工作原理,因为我们正在引入NRT ”。 NRT是程序员的注释,设计使它们对运行时的影响为零

我认为无法为引用类型指定默认值与无法为值类型定义无参数构造函数的情况类似-运行时需要快速的全零默认值和null值是引用类型的合理默认值。并非所有类型都具有合理的默认值-TcpClient的合理默认值是什么?

如果要使用自己的自定义默认值,请实现静态的Default方法或属性并将其记录下来,以便开发人员可以将其用作该类型的默认值。无需更改语言的基础。

我的意思是必须在C#实现的某个深处指定0int的默认值。我们也可以选择12,但没有,共识是0。那么,是否可以在激活Nullable引用类型功能时仅将string的默认值指定为String.Empty

正如我所说,最重要的是将内存范围归零非常快且方便。没有运行时组件负责检查给定类型的默认值是什么,并在创建新值时在数组中重复该值,因为这样做效率极低。

您的建议基本上意味着运行时必须在运行时以某种方式检查string的可空性元数据,并将全零的不可为空的string值视为空的{{1} }。对于空string的一种特殊情况,这将是涉及到运行时的非常复杂的更改。在为string分配null而不是明智的默认值时,仅使用静态分析器来警告您会节省更多成本。幸运的是,我们有这样的分析器,即NRT功能,该功能始终拒绝编译包含如下定义的类:

string

通过发出警告并强迫我将其更改为:

string Foo { get; set; }

(我建议您顺便打开“将警告视为错误,但这只是出于个人喜好。)

再次对我来说这没有意义,我认为Foo的默认值应该是new Foo()(如果没有无参数的构造函数,则会产生编译错误)。为什么默认情况下将不应该为null的对象设置为null?

除其他外,这将使您无法在没有默认构造函数的情况下声明引用类型的数组。大多数基本集合都使用数组作为基础存储,包括string Foo { get; set; } = ""; 。而且,每当您创建大小为List<T>的数组时,都将需要分配类型的N默认实例,这同样非常低效。构造函数也可能有副作用。我不会进一步考虑这会破坏多少东西,足以说这很难做。考虑到实施NRT到底有多复杂(Roslyn回购中的NullableReferenceTypesTests.cs文件仅具有〜130,000行代码),因此引入这种更改的成本效益不是...很好。

爆炸运算符(!)和可空值类型

第一行编译器就可以了,完全没有警告。我最近在体验可为空的引用类型功能时发现了N,并且我希望编译器也能适用于第二行,但事实并非如此。现在,我对于微软为何决定实施不同的行为感到非常困惑。

null!值仅对引用类型和可为空的值类型有效。可空类型再次定义为in the spec

可空类型可以表示其基础类型的所有值以及附加的null值。可空类型写为null ,其中T?是基础类型。该语法是T的简写,并且两种形式可以互换使用。 (...)可为空类型System.Nullable<T>的实例具有两个公共只读属性:

  • 类型为T?的{​​{1}}属性
  • 类型为HasValue的{​​{1}}属性 boolValue的实例被认为是非空的。非null实例包含一个已知值,T返回该值。

您无法为HasValue分配true的原因非常明显-Value是一个采用32位表示整数的值类型。 null值是一个特殊的参考值,其值为机器字大小,表示内存中的位置。将int分配给int没有明智的语义。 null的存在是专门用于允许将null分配给值类型以表示“无值”情况。但请注意,

int

是纯语法糖。 Nullable<T>的全零值是“无值”情况,因为这意味着nullint? x = null; 。在任何地方都没有分配魔法值Nullable<T>,这与说HasValue一样,它只是创建给定类型false的新全零结构并将其分配。>

因此,答案再次是-没有人故意设计使其与NRT不兼容的设计。自从C#2引入以来,可空值类型是该语言的一项更基本的功能。而且,您提议它的工作方式不会转化为明智的实现-您是否希望所有值类型都可以为空?那么所有这些人都必须拥有一个null字段,该字段占用一个额外的字节并可能填充填充(我认为将= default表示为40位类型而不是32位的语言异端:))。

bang运算符专门用于告诉编译器“我知道我正在引用nullable /将null赋值给non-nullable,但我比您聪明,并且我知道事实上这不会打破任何东西”。它禁用静态分析警告。但这并不能神奇地扩展基础类型以容纳T值。

摘要

考虑到它并不能完全防止将HasValue包含在不可为空的引用类型变量中,因此此新功能似乎并没有改变任何东西,也根本没有改善开发人员的生活(相对不能为int的非空值类型,因此不需要进行空检查)

因此,最后似乎唯一增加的价值仅仅是签名。现在,开发人员可以清楚地知道方法的返回值是否可以为null或属性可以为null(例如,在数据库表的C#表示形式中,允许使用NULL)值)。

来自the official docs on NRTs

此新功能相对于早期版本的C#(无法从变量声明中确定设计意图)中的参考变量的处理而言,具有显着的优势。编译器没有针对引用类型的null引用异常提供安全性(...)这些警告在编译时发出。编译器不会在可为空的上下文中添加任何空检查或其他运行时构造。在运行时,可为空的引用和不可为空的引用是等效的。

所以您说对了,“唯一增加的价值只是签名” 静态分析,这就是我们首先拥有签名的原因。这不是开发人员生活的改善吗?请注意,您的行

null

发出警告。如果您不忽略它(甚至更好,启用“将警告作为错误对待”),您将获得价值-编译器为您在代码中发现了一个错误。

它是否可以防止您在运行时将null分配给非空引用类型?否。它会改善开发人员的生活吗?千倍是。该功能的强大之处在于在编译时进行的警告和可为空的分析。如果您忽略NRT发出的警告,则后果自负。您可以忽略编译器的帮助之手的事实并不会使它毫无用处。毕竟,您也可以将整个代码放在null上下文中并用C编写程序,这并不意味着C#是无用的,因为您可以规避其安全保证。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res