一、可空类型
值类型必须包含一个值,它们可以在声明之后、赋值之前,在未赋值的状态下存在,但不能使用未赋值的变量。而引用类型可以是null。
有时让值类型为空时很有用的(尤其是在处理数据库时),泛型使用System.Nullable<T>类型提供了使值类型为空的一种方式。例如:
System.Nullable<int> nullableInt;
这行代码声明了一个变量nullableInt,它可以拥有int变量能包含的任意值,还可以拥有值null,所以可以编写如下的代码:
nullableInt = null;
前面的赋值语句等价于:
nullableInt = new System.Nullable<int>();
与其他任意变量一样,无论是初始化为null(使用上面的语法),还是通过给他赋值来初始化,都不能在初始化之前使用它。
可以项测试引用类型一样测试可空类型,看看它们是否为null:
if(nullableInt == null)
{
...
}
另外,可以使用HasValue属性:
if(nullableInt.HasValue)
{
...
}
尽管引用类型存在HasValue属性,但是引用并不能使用这种方法,因为如果引用类型为null的话,就说明不存在对象,自然不能通过对象来访问这个属性,否则就会抛出一个异常。
使用HasValue属性可以以查看可空类型的值。如果HasValue是true,就说明该可空变量并非空值。但如果返回的HasValue是false,则说明该可空变量是null,此时访问Value属性会抛出System.InvalidOperationException类型的异常。
可空类型非常有用,以至于它们修改了C#的语法。声明一个可空类型的变量不使用上述的语法,而是使用下面的语法:
int? nullableInt;
1)运算符和可空类型
对于简单类型(如int),可以使用+、-等运算符来处理值。而对于对应的可空类型,这是没有区别的:包含在可空类型中的值会隐式转换为需要的类型,使用适当的运算符。这也适用于结构和自己提供的而运算符。例如:
int? op1 = 5;
int? result = op1 *2;
注意,这里的result的类型也是int?,如果result的类型改为int,则代码不会被编译,需要进行显式转换,或是利用Value属性访问值:
int? op1 = 5;
int result = (int)op1 * 2;
int result = op1.Value * 2;
只要op1不为空值,上述的代码就可以正常运行。如果op1是null,就会生成System.InvalidOperationException类型的异常。
当运算表达式中的一个或两个值为null时,计算结果又该如何:
int? op1 = null;
int? op2 = 5;
int? result = op1 * op2;
答案是:对了除了bool?以外的所有简单可空类型,该操作的结果是null,可以把它解释为“不能计算”。
2)??运算符
为了进一步减少处理可空类型所需的代码量,使可空变量的处理变得更见到那,可以使用??运算符。这个运算符称为空接合运算符,是一个二元运算符,允许给可能等于null的表达式提供另一个值。如果第一个操作数不是null,该运算符就等于第一个操作数,否则,该运算符就等于第二个操作数。下面的两个表达式的作用是相同的:
op1 ?? op2;
op1 == null ? op2 : op1;
在这两行代码中,op1可以是任意可空表达式,包括也引用类型和更重要的可空类型。因此,如果可空类型是null,就可以使用??运算符提供需要使用的默认值,如下所示:
int? op1 = null;
int result = op1 * 2 ?? 5;
在这个示例中,op1是null,所以op1*2也是null,因此??运算符将值5赋予result。这里要特别注意,在结果放入int类型的变量result不需要进行显式转换,??运算符会自动处理这个转换。还可以把??表达式的结果传入int?中:
int? result = op1 * 2 ?? 5;
在处理可空变量时,??运算符有许多用途,它也是一种提供默认值的便捷方式,不需要使用if结构中的代码块或容易引起混淆的三元运算符。
3)?.运算符
这个操作符通常称为Elvis运算符或空条件运算符,有助于避免繁杂的空值检查造成的代码歧义。例如,如果像得到给定客户的订单数,就需要在设置计数值之前检查空值:
int count = 0;
if (customer.orders != null)
{
count = customer.orders.Count();
}
如果只是编译了这段代码,但客户没有订单(即为null),就会抛出System.ArgumentNullException:
int count = customer.orders.Count();
使用?.运算符,会把int?count设置为null,而不会抛出一个异常:
int? count = customer.orders?.Count();
配合??运算符,还可以给count赋一个null时的默认值:
int? count = customer.orders?.Count ?? 0;
空条件运算符的另一个用途是触发事件,触发事件的最常见方法是使用如下代码模式:
var onChanged = Onchanged;
if (onChanged != null)
{
onChanged(this, args);
}
这种模式不是线程安全的,因为有人会在null检查已经完成后,退订最后一个事件处理程序。此时会抛出异常,程序崩溃。使用空条件运算符可以避免这种情形:
Onchange?.Invoke(this,args);