深入理解C#(十)
*第四章(C# 2:可空类型)
C# 1中表示空值的模式
- 魔值:牺牲一个值来表示空值(DBNull.Value表示数据库返回null的情况,这个魔值表示所有类型的空值)
- 引用包装类型:直接用object作为变量类型,并根据需要进行装箱和拆箱
- 额外的布尔标志:使用一个普通的值类型的值,同时使用另一个布尔标志来表示值是“真正”存在还是被忽略。要么在代码中维护两个单独的变量,要么将值和标志封装到另一个值类型中。
System.Nullable<T>
和System.Nullable
Nullable<T>
是一个有着值类型约束的泛型类型。T的类型称为可空类型的基础类型(underlying type)。Nullable<T>
有两个重要属性:HasValue和Value。就是采用上面第三种实现模式。Nullable<T>
仍为值类型,值将直接包含一个bool和一个int,而不是其他对象的引用Nullable<T>
引入一个名为GetValueOrDefault的新方法。存在值就返回值否则返回默认值/自己指定的返回值。Nullable<T>
覆盖了其他方法:GetHashCode,ToString和Equals。GetHashCode在没有值时返回0,ToString在没有值时返回空字符串
再次强调Nullable<T>
是一个结构(值类型),如果把它转换成引用类型,需要对它装箱
调用first.Equals(second)的具体规则:
- first没有值,second为null,相等
- first没有值,second不为null,不相等
- first有值,second为null,不相等
- 否则,若first的值等于second,相等
System.Nullable类是一个静态类,只包含静态方法。
提供三个有用方法:
1 | public static int Compare<T>(Nullable<T> n1,Nullable<T> n2) |
Compare使用Comparer
第三个方法如果参数是一个可空类型,方法就返回它的基础类型;否则返回null。
C# 2为可空类型提供的语法糖
?修饰符
Nullable<byte>
与byte?
可相互转换
使用null进行赋值和比较
可空转换和操作符
如果一个非可空的值类型支持一个操作符或一种转换,且那个操作符或转换只涉及其他非可空的值类型时,那么对应可空的值类型也支持同样的操作符或转换。举例:int=》long,int?=》long?
具体细节待看
可空逻辑
对可空类型使用as操作符
在C# 2之前,as只能用于引用类型。在C# 2中,也可以用于可空类型。
空合并操作符(??)
first??second
- 对first进行求值;
- 如结果非空,该结果就是表达式的结果
- 否则求second的值,其结果为整个表达式的结果
这个操作符还可用于可空值类型+引用类型
这个操作符是右结合的
用法案例:
假设有一个在线订购系统,有billing address(账单寄送地址),contact address(联系地址),以及shipping address(送货地址)等,任何用户都必须有一个billing address,但contact address是可选的。对于一个订单来说,送货地址也是可选的,默认为billing address。遇到送货问题,要找联系人时。
C# 1实现:
1 | Address contact=user.ContactAddress; |
C# 2改进:
1 | Address contact=user.ContactAddress?? |
可空类型的新奇用法
尝试一个不使用输出参数的操作
输出参数的常规用法:用一个返回值来判断一个操作是否成功,并用一个输出参数来返回真正的结果。
返回引用类型的方法经常使用这样一种模式:失败时返回null,成功是返回非空值。但是,假如在方法执行成功的前提下,null也是一个有效的返回值,这样就不行了。
1 | ArrayList list=hash[key]; |
假设HashTable和Dictionary<Tkey,TValue>可以获取一个委托,每次查找到一个不存在的键时就调用这个委托来添加一个新值。使用这个模式适用这种情况。
使用可空类型,能将这种模式扩展至值类型。
具体实例:
1 | public static bool TryParse(string s, out Int32 result); |
这个int的方法的传统用法如下:
1 | int result; |
如果我们用可空类型:
class NullableTryParse
{
static int? TryParse(string data)
{
int ret;
if (int.TryParse(data, out ret))
{
return ret;
}
else
{
return null;
}
}
1 | static void Main() |
这样做的好处是它将返回值与解析是否成功封装在一个变量中,还把做和测试分离了。
还有一个好处是可以和空合并操作符配合使用。
小结一下:
上述模式的要点在于:先定义并获取方法返回值(做),再判断返回值(测试)。
如返回值是引用类型,我们可以通过判断是否为null,很容易实现这个模式,但若是值类型,就不好办了(除非我们定义一个特殊值,但这样会导致语义不明,就像魔数一样),所以就用到了可空类型,这样我们也能像使用引用类型一样,使用值类型。
更好的做法:在.NET 4中包含了Tuple类型,使用元祖可以使返回值含义更明确。
空合并操作符简化比较
问题背景:假设要写一个电子商务网站,且有一个产品列表。希望按流行度,价格,名称依次排序。
C# 1的实现:
1 | public int Compare(Product first,Product second) |
我们往往这样写Compare方法。但若是属性返回为null,我们在比较前还要先判断非空,这样代码就太复杂了。我们可以用空合并操作符来简化代码。
C# 2 ??操作符实现:
1 | public static class PartialComparer |
先实现一个用于比较的辅助类。可注意ReferenceCompare方法中的条件操作符用法。
现在可以重写Compare方法:
1 | public int Compare(Product first, Product second) |
最后的0指明前面所有比较都通过,这两个Product相等。也可用Comparer<string>.Default.Compare(first.Name,second.Name)
作比较。