.NET(C#) Internals: 鲜为人知的??

简介:

引言

"??"我很早就知道这它了,但我之前从没想过要拿它出来分享。主要是因为感觉它应该很基础,大家都知道,但是现在我发现身边很多人不知道"??"这个运算符!故有此文,如果您已经对??很熟悉了就没必要看此文了。本文主要内容如下:

  • 1、"??"运算符
  • 2、可空类型
  • 3、深入分析
  • 4、比较

1、"??"运算符

如果"??"运算符的左操作数非空,该运算符将返回左操作数,否则返回右操作数。格式如下:

?
1
X = A ?? B;

很明显它是一个二元运算符。如果A不为空的话,X=A;否则X=B。下面我们看一个例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Collections.Generic;
using System.Text;
namespace CsharpTest
{
class Program
{
static void Main( string [] args)
{
int a = 0;
int b = a ?? -1;
System.Console.WriteLine(b);
}
}
}

你觉得它会输出什么呢?它什么都不会输出,因为会发生编译时错误!会看有如下图所示错误:

image

图1、"??"编译时错误

是的,正如提示的那样"??"运算符两边的操作数不能都是int型。这是为何呢?我们来分析一下。这是因为值类型是不可判空的,例如int i可以为i=100,或,0或,或-1,但不可以为i=null。我们再看下面这段代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Collections.Generic;
using System.Text;
namespace CsharpTest
{
class Program
{
static void Main( string [] args)
{
string a = "yes" ; //if a=null, then b will equal to "no"
string b = a ?? "no" ;
System.Console.WriteLine(b);
}
}
}

我们运行这段代码就可以得到输出"yes”(如果a=null,则会输出"no”)。这是因为引用类型可以为null!

2、可空类型

那么是不是"??"运算符只能应用在引用类型上呢?或者说我们想应该在值类型上怎么办,例如int?这就要用到可空类型了。可空类型是System.Nullable 结构体的实例。可空类型可以表示其基础值类型正常范围内的值,再加上一个null 值。例如,Nullable<Int32>,读作“可空的Int32”,可以被赋值为-2147483648 到2147483647 之间的任意值,也可以被赋值为null 值。Nullable<bool> 可以被赋值为truefalse,或null。在处理数据库和其他包含可能未赋值的元素的数据类型时,将null 赋值给数值类型或布尔型的功能特别有用。例如,数据库中的布尔型字段可以存储值truefalse,或者该字段也可以未定义。

但为了简便语法 T? 是System.Nullable<T> 的简写,此处的T 为值类型。这两种形式可以互换。即int?与System.Nullable<int>是等价的。现在我们再看下面的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Collections.Generic;
using System.Text;
namespace CsharpTest
{
class Program
{
static void Main( string [] args)
{
int ? x = 1; //if x=null, then y will equal to -1. If you don't believe, you can try!
int y = x ?? -1;
System.Console.WriteLine(y);
}
}
}

这样的我们就可以对int型应用"??"运算符了!上面的代码会输出1。

可空类型具有以下特性:

  • 可空类型表示可被赋值为 null 值的值类型变量。无法创建基于引用类型的可空类型。(引用类型已支持 null 值。)。

  • 语法 T? 是 System.Nullable<T> 的简写,此处的 T 为值类型。这两种形式可以互换。

  • 为可空类型赋值与为一般值类型赋值的方法相同,如 int? x = 10; 或 double? d = 4.108;。

  • 如果基础类型的值为 null,请使用 System.Nullable.GetValueOrDefault 属性返回该基础类型所赋的值或默认值,例如 int j = x.GetValueOrDefault();

  • 请使用 HasValueValue 只读属性测试是否为空和检索值,例如 if(x.HasValue) j = x.Value;

    • 如果此变量包含值,则 HasValue 属性返回 True;或者,如果此变量的值为空,则返回 False。

    • 如果已赋值,则 Value 属性返回该值,否则将引发 System.InvalidOperationException

    • 可空类型变量的默认值将 HasValue 设置为 false。未定义 Value

  • 使用 ?? 运算符分配默认值,当前值为空的可空类型被赋值给非空类型时将应用该默认值,如 int? x = null; int y = x ?? -1;。

  • 不允许使用嵌套的可空类型。将不编译下面一行:Nullable<Nullable<int>> n;

3、深入分析

我们用ildasm查看最后那段代码,知道main函数的IL代码为:

??

图2、main函数IL代码

从上面的标号为1的红圈我们可以知道可空类型仍然是值类型。标号为2的红圈即为判断"??"运算符的左操作数是否有值get_HasValue(),接着标号为3的红圈对刚才的返回值进行判断:如果get_HasValue()的返回值为非零 ( true ),则按指定的偏移量分支到目标指令即将返回"??"运算符的左操作数如标号为4的红圈GetValueOrDefault();否则返回右操作数。

从上图我们可以知道这主要涉及的操作都是System.Nullable 结构体中的方法和属性,get_HasValue为System.Nullable 结构体中的属性而GetValueOrDefault()是System.Nullable 结构体中的方法(请查阅MSDN)。

如果是用ILDasm查看前面"??"运算符应用于字符串的IL代码,就不会涉及到这些方法和属性,如下图所示:

??

图3、"??"运算符应用于字符串的IL代码

4、比较

其实"??"运算符等价于:

?
1
if (A!= null )<br> X = B;<br> else <br> X = C;

但是如果用"??"运算符却简便一些,如判断一个id为txtID的TextBox是否为空,如果不空返回它的text;否则生成一个新的TextBox。可以这样写:

?
1
string result = ( txtID ?? new TextBox() ).Text;

还有就是前面提到的可空类型应用于数据库的情况用"??"运算符比较简便。

"??"运算符是不是也令你先到了"?:"三元运算符呢?"?:"三元运算符的格式如下:

?
1
X = A ? B : C;

这里A是一个布尔表达式,如果A为true则X=A;否则X=C。由于"?:"三元运算符起源于C系的编程语言,大家都比较熟悉了,这里就不深入了。

希望此文给你带来了信息量!




本文转自吴秦博客园博客,原文链接:http://www.cnblogs.com/skynet/archive/2010/05/21/1740996.html,如需转载请自行联系原作者

相关文章
|
16天前
|
数据可视化 网络协议 C#
C#/.NET/.NET Core优秀项目和框架2024年3月简报
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
|
1月前
|
SQL 数据库 C#
C# .NET面试系列十一:数据库SQL查询(附建表语句)
#### 第1题 用一条 SQL 语句 查询出每门课都大于80 分的学生姓名 建表语句: ```sql create table tableA ( name varchar(10), kecheng varchar(10), fenshu int(11) ) DEFAULT CHARSET = 'utf8'; ``` 插入数据 ```sql insert into tableA values ('张三', '语文', 81); insert into tableA values ('张三', '数学', 75); insert into tableA values ('李四',
65 2
C# .NET面试系列十一:数据库SQL查询(附建表语句)
|
1月前
|
开发框架 算法 搜索推荐
C# .NET面试系列九:常见的算法
#### 1. 求质数 ```c# // 判断一个数是否为质数的方法 public static bool IsPrime(int number) { if (number < 2) { return false; } for (int i = 2; i <= Math.Sqrt(number); i++) { if (number % i == 0) { return false; } } return true; } class Progr
58 1
|
1月前
|
并行计算 安全 Java
C# .NET面试系列四:多线程
<h2>多线程 #### 1. 根据线程安全的相关知识,分析以下代码,当调用 test 方法时 i > 10 时是否会引起死锁? 并简要说明理由。 ```c# public void test(int i) { lock(this) { if (i > 10) { i--; test(i); } } } ``` 在给定的代码中,不会发生死锁。死锁通常是由于两个或多个线程互相等待对方释放锁而无法继续执行的情况。在这个代码中,只有一个线程持有锁,且没有其他线程参与,因此不
105 3
|
3月前
|
Linux C# 开发工具
C#开源的一款友好的.NET SDK管理器
C#开源的一款友好的.NET SDK管理器
|
9天前
|
开发框架 前端开发 JavaScript
采用C#.Net +JavaScript 开发的云LIS系统源码 二级医院应用案例有演示
技术架构:Asp.NET CORE 3.1 MVC + SQLserver + Redis等 开发语言:C# 6.0、JavaScript 前端框架:JQuery、EasyUI、Bootstrap 后端框架:MVC、SQLSugar等 数 据 库:SQLserver 2012
|
1月前
|
开发框架 人工智能 .NET
C#/.NET/.NET Core拾遗补漏合集(持续更新)
C#/.NET/.NET Core拾遗补漏合集(持续更新)
|
1月前
|
SQL 存储 关系型数据库
C# .NET面试系列十:数据库概念知识
#### 1. 为什么要一定要设置主键? 设置主键是数据库设计中的一个重要概念,有几个主要原因: 1、唯一性 ```c# 主键必须保证表中的每一行都有唯一的标识。这样可以避免数据冗余和不一致性。如果没有主键或者主键不唯一,就可能出现数据混乱或错误。 ``` 2、查询性能 ```c# 数据库系统通常会使用主键来加速数据检索。主键通常会被索引,这样可以更快速地找到特定行的数据,提高查询效率。 ``` 3、关联性 ```c# 主键常常用于建立表与表之间的关系。在关系数据库中,一个表的主键通常与其他表中的外键建立关联,这种关系对于数据的一致性和完整性非常重要。 ``` 4、数据完
131 1
C# .NET面试系列十:数据库概念知识
|
1月前
|
XML 开发框架 .NET
C# .NET面试系列八:ADO.NET、XML、HTTP、AJAX、WebService
## 第二部分:ADO.NET、XML、HTTP、AJAX、WebService #### 1. .NET 和 C# 有什么区别? .NET(通用语言运行时): ```c# 定义:.NET 是一个软件开发框架,提供了一个通用的运行时环境,用于在不同的编程语言中执行代码。 作用:它为多语言支持提供了一个统一的平台,允许不同的语言共享类库和其他资源。.NET 包括 Common Language Runtime (CLR)、基础类库(BCL)和其他工具。 ``` C#(C Sharp): ```c# 定义: C# 是一种由微软设计的面向对象的编程语言,专门为.NET 平台开发而创建。 作
174 2
|
1月前
|
开发框架 中间件 .NET
C# .NET面试系列七:ASP.NET Core
## 第一部分:ASP.NET Core #### 1. 如何在 controller 中注入 service? 在.NET中,在ASP.NET Core应用程序中的Controller中注入服务通常使用<u>依赖注入(Dependency Injection)</u>来实现。以下是一些步骤,说明如何在Controller中注入服务: 1、创建服务 首先,确保你已经在应用程序中注册了服务。这通常在Startup.cs文件的ConfigureServices方法中完成。例如: ```c# services.AddScoped<IMyService, MyService>(); //
63 0