【More Effective C#】掩藏在Nullable<T>后的秘密

it2022-05-07  27

        对于可空类型,我们并不陌生.例如,当我们为我们的数据库生成一个实体时,例如LINQ2SQL,查看实体类中的字段,经常会发现.我们那些定义为可空的字段的类型后面都会多了个?,如int?string?char?,bool?等等.这就是今天的主角---可空类型.

Nullable<T>

         事实上,所有的可空类型都继承自Nullable<T>类,而编译器为了方便大众,提供了几个常用的可空类型,可空基本类型.可空类型,顾名思义,它是可空的.这意味着,与非可空类型相比.可空类型需要更多的检查.可空类型为非可空类型添加了一种丢失或不可用的状态.日常项目中.可空类型似乎可以为我们带来很多的好处.

public static int? DefaultParse(this string input) { int answer; return int.TryParse(input, out answer) ? answer : default(int?); }

  这个扩展方法看起来不会出什么错.实际上.它也能很好的运行.并很大程度上会优化我们的代码.但实际上.它也可能引入一些你可能不能预料到的问题.

Nullable<T>的运算

可空数值类型提供了类似浮点数和NaN之间的语义,所有涉及NaN的比较都将返回false.

double d = 0; Console.WriteLine(d > double.NaN); //false Console.WriteLine(d < double.NaN); //false Console.WriteLine(double.NaN > double.NaN); //false Console.WriteLine(double.NaN == double.NaN); //false

与NaN有区别的是.可空类型支持空值之间的等同性比较

int? nullableOne = default(int?); int? nullableTwo = 0; int? nullableThree = default(int?); Console.WriteLine(nullableOne < nullableTwo); //false Console.WriteLine(nullableOne > nullableTwo); //false Console.WriteLine(nullableOne == nullableThree); //true

涉及可空数值类型的操作与NaN数值的操作的行为完全相同.

double d = 0; Console.WriteLine(d + double.NaN); //NaN Console.WriteLine(d - double.NaN); //NaN Console.WriteLine(d * double.NaN); //NaN Console.WriteLine(d / double.NaN); //NaN int? nullableOne = default(int?); int? nullableTwo = default(int); Console.WriteLine((nullableOne + nullableTwo).HasValue); //false //....

Nullable<T>中提供了一个GetValueORDefault实现.可以获取相关可空类型的默认值.这样,我们必须频繁的调用GetValueORDefault,为了减轻我们的工作量与抱怨.减少我们砸键盘的次数.所以,C#中的??出现了.

??操作符

??操作符,也叫空值合并运算符.它是一个二元运算符.左边为可空类型,判读左边的值是否为空,若为空,则返回右边的值,否则返回左边的值.例如

int? result = 123; return result ?? 321; //return 123; int? result = default(int?); return result ?? 321; //return 321;

Nullable<T>信息丢失

在很多情况下.我们很Happy的使用着可空类型.但很多时候并难以理清某些特别情况下可空类型返回的结果.

int? f = default(int?); XmlSerializer x = new XmlSerializer(typeof(int?)); StringWriter t = new StringWriter(); x.Serialize(t, f);

这是一段序列化可空类型int的实现.生成的XML如下.

可以看到.类型是int,却没有内容.这样,我们很难看出这段代码其实是由int?类型序列化生成的.信息丢失了.并且在反序列化时,你将十分痛恨自己为什么要使用可空类型.

string storage = t.ToString(); StringReader s = new StringReader(storage); var f2 = (int)x.Deserialize(s); //f2不能为空 Console.WriteLine(f2);

除了序列化操作会信息丢失,在类型转换时.也会发生信息的丢失.

int? defaultNullable = default(int?); string s = defaultNullable.ToString(); //转换成string的空内容""或string.Empty Console.Write(s); string s2 = ((object)defaultNullable).ToString(); //装箱后为null,ToString操作不能为空出错 Console.Write(s2);

最小化可空类型的可见范围

经过以上的"血泪史".我们终于可以看清Nullable<T>后面的一些小秘密.可空类型,如一把双刃剑,只有在更好的理解了它的一些特性之后.你才能将其使用得更加的得心应手.发挥其最大的效应.反之,它将给你带来无穷无尽的懊恼很悔恨(不断的咒骂着..该死的可空类型,该死的可空类型.).所以,最小化可空类型的可见范围(避免使用)将让我们的类型更易于使用,且不容易被误用.写出更高质量的代码.

转载于:https://www.cnblogs.com/kongyiyun/archive/2010/10/27/1862094.html


最新回复(0)