C# 中 ? 、??、?:、 ?.、 ?[] 的含义及用法

3,254 阅读6分钟

C# 中变量定义带 ? 及 ?? 的含义及用法 1

正文

?

C# 语法中一个问号(?)的运算符是指:可以为 null 的类型。

MSDN 上面的解释

在处理数据库和其他包含不可赋值的元素的数据类型时,将 null 赋值给 数值类型布尔型 以及 日期类型 的功能特别有用。

例如,数据库中的布尔型字段可以存储值 true 或 false,或者,该字段也可以未定义。

??

C# 语法中两个问号(??)的运算符是指 null 合并运算符,合并运算符为类型转换定义了一个预设值,以防可空类型的值为 null

MSDN 上面的解释

?? 运算符称为 null 合并运算符,用于定义可以为 null 值的类型和引用类型的 默认值。如果此运算符的左操作数不为 null,则此运算符将返回左操作数(左边表达式);否则当左操作数为 null,返回右操作数(右边表达式)。 

int? x = null;//定义可空类型变量
int? y = x ?? 1000;//使用合并运算符,当变量 x 为 null 时,预设赋值 1000
 
Console.WriteLine(y.ToString()); //1000
 
/// <summary>
/// Gets a single instance
/// </summary>
public static Log LogInstance
{
      get
      {
           return _log ?? (_log = new Log()); //如果此运算符的左操作数不为 null,则此运算符将返回左操作数;否则返回右操作数。
       }
}

C# 中 ?、 ??、 ?: 、?.、?[ ] 2

1. 可空类型修饰符 ?

引用类型可以使用空引用表示一个不存在的值,而值类型通常不能表示为空。

例如:string str=null; 是正确的,int i=null; 编译器就会报错。

为了使值类型也可为空,就可以使用可空类型,即用可空类型修饰符 ? 来表示,表现形式为 T?

例如:

int? 表示可空的整型,

DateTime? 表示可为空的时间。

T? 其实是 System.Nullable(泛型结构)的缩写形式, 也就意味着当你用到 T? 时编译器编译时会把 T? 编译成 System.Nullable 的形式。

例如:int? ,编译后便是 System.Nullable 的形式。

2. 三元(运算符)表达式 ?:

例如:x?y:z 表示如果表达式 xtrue,则返回 y

如果 xfalse,则返回 z,是省略 if{}else{} 的简单形式。

3. 空合并运算符 ??

用于定义可空类型和引用类型的 默认值

如果此运算符的左操作数不为 null,则此运算符将返回左操作数,否则返回右操作数。

例如:a??banull 时则返回 ba 不为 null 时则返回 a 本身。

空合并运算符为 结合运算符,即操作时 从右向左 进行组合的。

如,a??b??c 的形式按 a??(b??c) 计算。

4. NULL 检查运算符 ?.

例如我们要获取一个 Point 序列的第一个点的 X 坐标,第一感觉会这么写:

int firstX = points.First().X;

但是,老鸟会告诉你,这儿没有进行 NULL 检查,正确的版本是这样的:

int? firstX = null;
if (points != null)
{
    var first = points.FirstOrDefault();
    if (first != null)
    firstX = first.X;
}

正确倒是正确了,代码取变得难读多了。在 C# 6.0 中,引入了一个 ?. 的运算符,前面的代码可以改成如下形式:

int? firstX = points?.FirstOrDefault()?.X;

从这个例子中我们也可以看出它的基本用法:如果对象为 NULL,则不进行后面的获取成员的运算,直接返回NULL

需要 注意 的是,由于 ?. 运算符返回的可以是 NULL,当返回的成员类型是 struct 类型的时候,?.. 运算符的返回值类型是不一样的。

Point p = new Point(3, 2);
Console.WriteLine(p.X.GetType() == typeof(int)); //true
Console.WriteLine(p?.X.GetType() == typeof(int?)); //true

5 ?[] 运算符:

int? first = customers?[0].Orders.Count();

来自豆包的解释

这段代码是 C# 中用于检查一个名为 customers 的集合的第一个元素是否存在。

  • 首先,customers?[0] 表示检查 customers 集合是否为空(null)。? 操作符是一个空值合并操作符,它允许在访问集合中的元素之前检查集合是否存在。这里,customers?[0] 表示尝试访问 customers 集合的第一个元素。==如果 customers 存在且它的第一个元素存在,代码将继续执行==;如果 customersnull 或者它没有任何元素,整个表达式将返回 null

  • 然后,Orders.Count() 表示获取 customers 集合的第一个元素的 Orders 集合的计数。Orders 可能是 customers 集合中每个元素的一个属性,它指向一个 Order 对象的集合。Count() 方法返回这个 Order 对象集合的数量。

  • 综上所述,customers?[0].Orders.Count() 这个表达式的意思是:尝试获取 customers 集合的第一个元素,并返回这个元素的 Orders 集合的数量。如果 customers 或者它的第一个元素不存在,整个表达式将返回 null

请注意int? 类型表示一个可空整数,即它可以存储 null 值。这使得表达式的结果可以是一个整数或者 null,具体取决于 customers 集合的状态。这种设计提供了一种灵活的方式来处理可能为空的集合,避免了运行时错误的发生。

C# 中 "?"(问号)相关语法糖 3

c# 中与 ?(问号有关的语法糖大概有这几个:????.?[]?: ,在前面已经有详细说明。总结来看,除了 ?: 这个三元(运算符)表达式外,都是和是否空有关。但文章中对 ?[] 未做更多描述,所以在这里做个补充。看看下面的栗子:

string[] arr = null;
Console.WriteLine(arr[0]);

上面代码将报错:“Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.

string[] arr = null;
Console.WriteLine(arr?[0]);

那么上面代码 不会报错,而是输出空白。

string[] arr = Array.Empty<string>();
Console.WriteLine(arr?[0]);

那么上面代码依旧会报错:“System.IndexOutOfRangeException:“Index was outside the bounds of the array.””。也就是 ? 只能判断某个值是否为 null,而无法判断 是否是空数组

int?[] arr = new int?[5];
arr[1] = 99;
arr[3] = 321;
foreach (int? i in arr)
{
    Console.WriteLine(i);
}

输出是什么?

   
 99 
  
 321 
  

如果把 ? 去掉,其余不变,也就是这样:

int[] arr = new int[5];

那么输出是什么?

 0 
 99 
 0 
 321 
 0

所以,本质上 ? 都是判断是否为 null。对于上面的例子,如果不加 ?,那么对于 int [] 这样的数组,即便不做初始化,系统也会自动给默认值,而这很容易造成错误 —— 因为你不知道数组中这个值到底是系统给的,还是你自己设置的。而当加了 ?,允许为空后,那么就不一样了,系统是不会给出默认值的,从而避免了一些不必要的错误 —— 这种错误往往还很难发现

Footnotes

  1. C# 中变量定义带问号及双问号的含义及用法

  2. C# 中 ?、 ??、 ?: 、?.、?[ ]

  3. C#中 "?"(问号)相关语法糖