可空类型
值类型必须包含一个值,它们可以在声明之后、赋值之前,在未赋值的状态下存在,但不能使用未赋值的变量,而引用类型可以是null。
值类型不可为null,如下图所示:
引用类型可以为null,如下图所示:
有时让值类型为空是很有用的,泛型使用了System.Nullable类型提供了使值类型为空的一种方式。
这样即使给值类型赋值null,编译器也不会报错,如下图所示:
现在可以像测试引用类型一样测试可空类型,看看它们是否为null,如下所示:
if (a == null)
{
//SomeCode
}
另外还可以使用HasValue属性,如下所示:
if(a.HasValue)
{
//SomeCode
}
但是这不适用于引用类型,即使引用类型有一个HasValue属性,也不能使用这种方法,因为引用类型的变量值为null,就表示不存在对象,当然就不能通过对象来访问这个属性,否则会抛出一个异常。
可使用Value属性查看可空类型的值。如果HasValue为true,就说明Value属性有一个非空值。但如果HasValue是false,就说明变量被赋予了null,访问Value属性会抛出异常,如下所示:
可空类型非常有用,C#也为我们提供了关于可空类型的语法糖,声明一个可空类型,也可以使用如下的语法:
int? a;
int?就是System.Nullable的缩写。
看到这里,可能很多朋友和我有一样的疑问,好像可空类型的存在就是为了让值类型可以为空,但是在平时经常看到string?的用法,string不是一个引用类型吗?不是本身就可以为null吗?
在网上搜索之后,我才知道在C#中,string
类型虽然被设计为引用类型,但它表现得像值类型。实际上,string
类型的对象在内存中是以引用的形式存储的,但由于它是不可变的(immutable),在许多方面表现得像值类型。
当你对一个字符串进行操作时,实际上是创建了一个新的字符串对象,而原始字符串对象保持不变。这种不可变性使得字符串的操作更安全,同时也更容易进行优化。
需要注意的是,C#中的其他值类型(如int
、double
等)是直接存储其值而不是引用,而string
是一个例外,被设计为引用类型。
??运算符
??运算符被称为空接合运算符(null coalescing operator),是一个二元运算符,允许给可能等于null的表达式提供另一个值。如果第一个操作数不是null,该运算符就等于第一个操作数,否则,该运算符就等于第二个操作数。
下面的两个表达式的作用是相同的:
a ?? b
a == null ? a : b
可以使用??运算符为可空类型为null时,提供其他一个值,如下所示:
static void Main(string[] args)
{
int? a = null;
int result = a * 2 ?? 6;
Console.WriteLine(result);
}
运行结果如下所示:
因为a为null,a*2也为null,所以等于第二个表达式,如果第一个表达式不为null,则为第一个表达式的值,如下所示:
static void Main(string[] args)
{
int? a = 2;
int result = a * 2 ?? 6;
Console.WriteLine(result);
}
运行结果如下所示:
第一个表达式a*2=4不为null,则结果为第一个表达式的值,即结果等于4。
?.运算符
?.运算符通常称为空条件运算符,有助于避免繁杂的空值检查造成的代码歧义。
比如,想要得到给定客户的订单数,就需要在设置计数之前检查空值,如下所示:
int count = 0;
if(customer.orders != null)
{
count = customer.orders.Count();
}
因为如果只编写
int count = customer.orders.Count();
那么当没有订单时(即为null)时,就会抛出System.ArgumentNullException异常。
使用?.运算符,会把int? count设置为null,而不是抛出一个异常。
int? count = customer.orders?.Count();
结合??运算符和?.运算符,就可以在结果为null时设置一个默认值。
int? count = customer.orders?.Count() ?? 0;
参考
《C#入门经典(第7版)》——Benjamin Perkins、Jacob Vibe Hammer、Jon D. Reid
如果你也对C#感兴趣,欢迎关注微信公众号,DotNet学习交流,一起成长进步~