引用自:http://www.cnblogs.com/anytao/category/155694.html

作者:Anytao

说在,开篇之前

null、 nullable、??运算符、null object模式,这些闪亮的概念在你眼前晃动,我们有理由相信“存在即合理”,事实上,null不光合理,而且重要。本文,从null的基本认知开始, 逐层了解可空类型、??运算符和null object模式,在循序之旅中了解不一样的null。

你必须知道的.NET,继续全新体验,分享更多色彩。

www.anytao.com

1 从什么是null开始?

null,一个值得尊敬的数据标识。

一般说来,null表示空类型,也就是表示什么都没有,但是“什么都没有”并不意味“什么都不是”。实际上,null是如此的重要,以致于在 JavaScript中,Null类型就作为5种基本的原始类型之一,与Undefined、Boolean、Number和String并驾齐驱。这种 重要性同样表现在.NET中,但是一定要澄清的是,null并不等同于0,"",string.Empty这些通常意义上的“零”值概念。相反,null 具有实实在在的意义,这个意义就是用于标识变量引用的一种状态,这种状态表示没有引用任何对象实例,也就是表示“什么都没有”,既不是Object实例,也不是User实例,而是一个空引用而已。

在上述让我都拗口抓狂的表述中,其实中心思想就是澄清一个关于null意义的无力诉说,而在.NET中null又有什么实际的意义呢?

在.NET中,null表示一个对象引用是无效的。作为引用类型变量的默认值,null是针对指针(引用)而言的,它是引用类型变量的专属概念,表示一个引用类型变量声明但未初始化的状态,例如:

object obj = null;

此时obj仅仅是一个保存在线程栈上的引用指针,不代表任何意义,obj未指向任何有效实例,而被默认初始化为null。

object obj和object obj = null的区别?

那么,object obj和object obj = null有实际的区别吗?答案是:有。主要体现在编译器的检查上。默认情况下,创建一个引用类型变量时,CLR即将其初始化为null,表示不指向任何有效实例,所以本质上二者表示了相同的意义,但是有有所区别:

// Copyright   : www.anytao.com

// Author      : Anytao,http://www.anytao.com

// Release     : 2008/07/31 1.0

//编译器检测错误:使用未赋值变量obj

//object obj;

//编译器理解为执行了初始化操作,所以不引发编译时错误

object obj = null;

if (obj == null)

{

//运行时抛出NullReferenceException异常

Console.WriteLine(obj.ToString());

}

:当我把这个问题抛给几个朋友时,对此的想法都未形成统一的共识,几位同志各有各的理解,也各有个的道理。当然,我也慎重的对此进行了一番探讨和分析,但是并未形成完全100%确定性的答案。不过,在理解上我更倾向于自己的分析和判断,所以在给出上述结论的基础上,也将这个小小的思考留给大家来探讨,好的思考和分析别忘了留给大家。事实上,将

static void Main(string[] args)

{

object o;

object obj = null;

}

反编译为IL时,二者在IL层还是存在一定的差别:

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

.maxstack 1

.locals init (

[0] object o,

[1] object obj)

L_0000: nop

L_0001: ldnull

L_0002: stloc.1

L_0003: ret

}

前者没有发生任何附加操作;而后者通过ldnull指令推进一个空引用给evaluation stack,而stloc则将空引用保存。

回到规则

在.NET中,对null有如下的基本规则和应用:

  • null为引用类型变量的默认值,为引用类型的概念范畴。
  • null不等同于0,"",string.Empty。
  • 引用is或as模式对类型进行判断或转换时,需要做进一步的null判断。

快捷参考

  • 关于is和as模式,可以参考《你必须知道的.NET》 7.5节“恩怨情仇:is和as”
  • 第一回:恩怨情仇:is和as

www.anytao.com

  • 判断一个变量是否为null,可以应用==或!=操作符来完成。
  • 对任何值为nul的l变量操作,都会抛出NullReferenceException异常。

2 Nullable<T>(可空类型)

一直以来,null都是引用类型的特有产物,对值类型进行null操作将在编译器抛出错误提示,例如:

//抛出编译时错误

int i = null;

if (i == null)

{

Console.WriteLine("i is null.");

}

正如示例中所示,很多情况下作为开发人员,我们更希望能够以统一的方式来处理,同时也希望能够解决实际业务需求中对于“值”也可以为“空”这一实际 情况的映射。因此,自.NET 2.0以来,这一特权被新的System.Nullable<T>(即,可空值类型)的诞生而打破,解除上述诟病可以很容易以下面的方式被实现:

//Nullable<T>解决了这一问题

int? i = null;

if (i == null)

{

Console.WriteLine("i is null.");

}

你可能很奇怪上述示例中并没有任何Nullable的影子,实际上这是C#的一个语法糖,以下代码在本质上是完全等效的:

int? i = null;

Nullable<int> i = null;

显然,我们更中意以第一种简洁而优雅的方式来实现我们的代码,但是在本质上Nullable<T>和T?他们是一路货色。

可空类型的伟大意义在于,通过Nullable<T>类型,.NET为值类型添加“可空性”,例如 Nullable<Boolean>的值就包括了true、false和null,而Nullable<Int32>则表示值即可以为整形也可以为null。同时,可空类型实现了统一的方式来处理值类型和引用类型的“空”值问题,例如值类型也可以享有在运行时以 NullReferenceException异常来处理。

另外,可空类型是内置于CLR的,所以它并非c#的独门绝技,VB.NET中同样存在相同的概念。

Nullable的本质(IL)

那么我们如何来认识Nullable的本质呢?当你声明一个:

Nullable<Int32> count = new Nullable<Int32>();

时,到底发生了什么样的过程呢?我们首先来了解一下Nullable在.NET中的定义:

public struct Nullable<T> where T : struct

{

private bool hasValue;

internal T value;

public Nullable(T value);

public bool HasValue { get; }

public T Value { get; }

public T GetValueOrDefault();

public T GetValueOrDefault(T defaultValue);

public override bool Equals(object other);

public override int GetHashCode();

public override string ToString();

public static implicit operator T?(T value);

public static explicit operator T(T? value);

}

根据上述定义可知,Nullable本质上仍是一个struct为值类型,其实例对象仍然分配在线程栈上。其中的value属性封装了具体的值类型,Nullable<T>进行初始化时,将值类型赋给value,可以从其构造函数获知:

public Nullable(T value)

{

this.value = value;

this.hasValue = true;

}

同时Nullable<T>实现相应的Equals、ToString、GetHashCode方法,以及显式和隐式对原始值类型与可空类型的转换。因此,在本质上Nullable可以看着是预定义的struct类型,创建一个Nullable<T>类型的IL表示可以非常 清晰的提供例证,例如创建一个值为int型可空类型过程,其IL可以表示为:

.method private hidebysig static void Main() cil managed

{

.entrypoint

.maxstack 2

.locals init (

[0] valuetype [mscorlib]System.Nullable`1<int32> a)

L_0000: nop

L_0001: ldloca.s a

L_0003: ldc.i4 0x3e8

L_0008: call instance void [mscorlib]System.Nullable`1<int32>::.ctor(!0)

L_000d: nop

L_000e: ret

}

对于可空类型,同样需要必要的小结:

  • 可空类型表示值为null的值类型。
  • 不允许使用嵌套的可空类型,例如Nullable<Nullable<T>> 。
  • Nullable<T>和T?是等效的。
  • 对可空类型执行GetType方法,将返回类型T,而不是Nullable<T>。
  • c#允许在可空类型上执行转换和转型,例如:

int? a = 100;

Int32 b = (Int32)a;

a = null;

  • 同时为了更好的将可空类型于原有的类型系统进行兼容,CLR提供了对可空类型装箱和拆箱的支持。

3 ??运算符

在实际的程序开发中,为了有效避免发生异常情况,进行null判定是经常发生的事情,例如对于任意对象执行ToString()操作,都应该进行必要的null检查,以免发生不必要的异常提示,我们常常是这样实现的:

object obj = new object();

string objName = string.Empty;

if (obj != null)

{

objName = obj.ToString();

}

Console.WriteLine(objName);

然而这种实现实在是令人作呕,满篇的if语句总是让人看着浑身不适,那么还有更好的实现方式吗,我们可以尝试(? :)三元运算符:

object obj = new object();

string objName = obj == null ? string.Empty : obj.ToString();

Console.WriteLine(objName);

上述obj可以代表任意的自定义类型对象,你可以通过覆写ToString方法来输出你想要输出的结果,因为上述实现是如此的频繁,所以.NET 3.0中提供了新的操作运算符来简化null值的判断过程,这就是:??运算符。上述过程可以以更加震撼的代码表现为:

// Copyright   : www.anytao.com

// Author      : Anytao,http://www.anytao.com

// Release     : 2008/07/31 1.0

object obj = null;

string objName = (obj ?? string.Empty).ToString();

Console.WriteLine(objName);

那么??运算符的具体作用是什么呢?

??运算符,又称为null-coalescing operator,如果左侧操作数为null,则返回右侧操作数的值, 如果不为null则返回左侧操作数的值。它既可以应用于可空类型,有可以应用于引用类型。

插播广告,我的新书

4 Nulll Object模式

模式之于设计,正如秘笈之于功夫。正如我们前文所述,null在程序设计中具有举足轻重的作用,因此如何更优雅的处理“对象为空”这一普遍问题,大 师们提出了Null Object Pattern概念,也就是我们常说的Null Object模式。例如Bob大叔在《敏捷软件开发--原则、模式、实践》一书,Martin Fowler在《Refactoring: Improving the Design of Existing Code》一书,都曾就Null Object模式展开详细的讨论,可见23中模式之外还是有很多设计精髓,可能称为模式有碍经典。但是仍然值得我们挖据、探索和发现。
下面就趁热打铁,在null认识的基础上,对null object模式进行一点探讨,研究null object解决的问题,并提出通用的null object应用方式。
解决什么问题?
简单来说,null object模式就是为对象提供一个指定的类型,来代替对象为空的情况。说白了就是解决对象为空的情况,提供对象“什么也不做”的行为,这种方式看似无 聊,但却是很聪明的解决之道。举例来说,一个User类型对象user需要在系统中进行操作,那么典型的操作方式是:

if (user != null)

{

manager.SendMessage(user);

}

这种类似的操作,会遍布于你的系统代码,无数的if判断让优雅远离了你的代码,如果大意忘记null判断,那么只有无情的异常伺候了。于 是,Null object模式就应运而生了,对User类实现相同功能的NullUser类型,就可以有效的避免繁琐的if和不必要的失误:

// Copyright   : www.anytao.com

// Author      : Anytao,http://www.anytao.com

// Release     : 2008/07/31 1.0

public class NullUser : IUser

{

public void Login()

{

//不做任何处理

}

public void GetInfo() { }

public bool IsNull

{

get { return true; }

}

}

IsNull属性用于提供统一判定null方式,如果对象为NullUser实例,那么IsNull一定是true的。

那么,二者的差别体现在哪儿呢?其实主要的思路就是将null value转换为null object,把对user == null这样的判断,转换为user.IsNull虽然只有一字之差,但是本质上是完全两回事儿。通过null object模式,可以确保返回有效的对象,而不是没有任何意义的null值。同时,“在执行方法时返回null object而不是null值,可以避免NullReferenceExecption异常的发生。”,这是来自Scott Dorman的声音。

通用的null object方案

下面,我们实现一种较为通用的null object模式方案,并将其实现为具有.NET特色的null object,所以我们采取实现.NET中INullable接口的方式来实现,INullable接口是一个包括了IsNull属性的接口,其定义为:

public interface INullable

{

// Properties

bool IsNull { get; }

}

仍然以User类为例,实现的方案可以表达为:

图中仅仅列举了简单的几个方法或属性,旨在达到说明思路的目的,其中User的定义为:

// Copyright   : www.anytao.com

// Author      : Anytao,http://www.anytao.com

// Release     : 2008/07/31 1.0

public class User : IUser

{

public void Login()

{

Console.WriteLine("User Login now.");

}

public void GetInfo()

{

Console.WriteLine("User Logout now.");

}

public bool IsNull

{

get { return false; }

}

}

而对应的NullUser,其定义为:

// Copyright   : www.anytao.com

// Author      : Anytao,http://www.anytao.com

// Release     : 2008/07/31 1.0

public class NullUser : IUser

{

public void Login()

{

//不做任何处理

}

public void GetInfo() { }

public bool IsNull

{

get { return true; }

}

}

同时通过UserManager类来完成对User的操作和管理,你很容易思考通过关联方式,将IUser作为UserManger的属性来实现,基于对null object的引入,实现的方式可以为:

// Copyright   : www.anytao.com

// Author      : Anytao,http://www.anytao.com

// Release     : 2008/07/31 1.0

class UserManager

{

private IUser user = new User();

public IUser User

{

get { return user; }

set

{

user = value ?? new NullUser();

}

}

}

当然有效的测试是必要的:

public static void Main()

{

UserManager manager = new UserManager();

//强制为null

manager.User = null;

//执行正常

manager.User.Login();

if (manager.User.IsNull)

{

Console.WriteLine("用户不存在,请检查。");

}

}

通过强制将User属性实现为null,在调用Login时仍然能够保证系统的稳定性,有效避免对null的判定操作,这至少可以让我们的系统少了很多不必要的判定代码。

详细的代码可以通过本文最后的下载空间进行下载。实际上,可以通过引入Facotry Method模式来构建对于User和NullUser的创建工作,这样就可以完全消除应用if进行判断的僵化,不过那是另外一项工作罢了。

当然,这只是null object的一种实现方案,在此对《Refactoring》一书的示例进行改良,完成更具有.NET特色的null object实现,你也可以请NullUser继承Use并添加相应的IsNull判定属性来完成。

借力c# 3.0的Null object

在C# 3.0中,Extension Method(扩展方法)对于成就LINQ居功至伟,但是Extension Method的神奇远不是止于LINQ。在实际的设计中,灵活而巧妙的应用,同样可以给你的设计带来意想不到的震撼,以上述User为例我们应用 Extension Method来取巧实现更简洁IsNull判定,代替实现INullable接口的方法而采用更简单的实现方式。重新构造一个实现相同功能的扩展方法,例 如:

// Copyright   : www.anytao.com

// Author      : Anytao,http://www.anytao.com

// Release     : 2008/07/31 1.0

public static class UserExtension

{

public static bool IsNull(this User user)

{

return null == user;

}

}

当然,这只是一个简单的思路,仅仅将对null value的判断转换为null object的判断角度来看,扩展方法带来了更有效的、更简洁的表现力。

null object模式的小结

  • 有效解决对象为空的情况,为值为null提供可靠保证。
  • 保证能够返回有效的默认值,例如在一个IList<User> userList中,能够保证任何情况下都有有效值返回,可以保证对userList操作的有效性,例如:

// Copyright   : www.anytao.com

// Author      : Anytao,http://www.anytao.com

// Release     : 2008/07/31 1.0

public void SendMessageAll(List<User> userList)

{

//不需要对userList进行null判断

foreach (User user in userList)

{

user.SendMessage();

}

}

  • 提供统一判定的IsNull属性。可以通过实现INullable接口,也可以通过Extension Method实现IsNull判定方法。
  • null object要保持原object的所有成员的不变性,所以我们常常将其实现为Sigleton模式。
  • Scott Doman说“在执行方法时返回null object而不是null值,可以避免NullReferenceExecption异常的发生”,这完全是对的。

5 结论

虽然形色匆匆,但是通过本文你可以基本了解关于null这个话题的方方面面,堆积到一起就是对一个概念清晰的把握和探讨。技术的魅力,大概也正是如此而已吧,色彩斑斓的世界里,即便是“什么都没有”的null,在我看来依然有很多很多。。。值得探索、思考和分享。

还有更多的null,例如LINQ中的null,SQL中的null,仍然可以进行探讨,我们将这种思考继续,所收获的果实就越多。

【转】[你必须知道的.NET]第二十一回:认识全面的null相关推荐

  1. [你必须知道的.NET]第二十一回:认识全面的null

    <你必须知道的.NET>网站 | Anytao技术博客  [你必须知道的.NET]第二十一回:认识全面的null 发布日期:2008.7.31 作者:Anytao © 2008 Anyta ...

  2. [你必须知道的.NET]第二十七回:interface到底继承于object吗?

    <你必须知道的.NET>网站 | Anytao技术博客  [你必须知道的.NET]第二十七回:interface到底继承于object吗? 发布日期:2009.03.05 作者:Anyta ...

  3. [你必须知道的.NET]第二十三回:品味细节,深入.NET的类型构造器

    1 引言 今天Artech兄在<关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释>一文中让我们认识了一个关于类型构造器调用执行的有趣 ...

  4. [你必须知道的.NET]第二十回:学习方法论

    本文,源自我回答刚毕业朋友关于.NET学习疑惑的回复邮件. 本文,其实早计划在<你必须知道的.NET>写作之初的后记部分,但是因为个中原因未能如愿,算是补上本书的遗憾之一. 本文,作为[& ...

  5. [你必须知道的.NET]第十一回:参数之惑---传递的艺术(上)

    本文将介绍以下内容: 按值传递与按引用传递深论 ref和out比较 参数应用浅析 1. 引言 接上回<第九回:品味类型---值类型与引用类型(中)-规则无边>中,对值类型和引用类型的讨论, ...

  6. [你必须知道的.NET]第二十四回:认识元数据和IL(上)

    说在,开篇之前 很早就有说说Metadata(元数据)和IL(中间语言)的想法了,一直在这篇开始才算脚踏实地的对这两个阶级兄弟投去些细关怀,虽然来得没有<第一回:恩怨情仇:is和as>那么 ...

  7. [你必须知道的css系列]第一回:丰富的利器2:CSS选择符之子选择符、相邻选择符...

    对了,接下来要讲的属性选择符,相邻选择符,子对象选择符可能大家稍微有点陌生了,这当然也是有原因的,因为IE6及以下的浏览器并不支持这几个选择符,而 大多数从事这方面工作的技术人员,多数时候还是主要考虑 ...

  8. [你必须知道的.NET] 第五回:深入浅出关键字---把new说透(转载)

    [你必须知道的.NET] 第五回:深入浅出关键字---把new说透 作者:Anytao 本文将介绍以下内容: 面向对象基本概念 new关键字深入浅出 对象创建的内存管理 1. 引言 园子里好像没有或者 ...

  9. [你必须知道的.NET]第二十二回:字符串驻留(上)---带着问题思考

    发布日期:2008.8.27 作者:Anytao  © 2008 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 走钢丝的人,在刺激中体验快感.带着问题思考,在问 ...

最新文章

  1. CUMCM之2006B:2006之B题: 艾滋病疗法的评价及疗效的预测
  2. 通过ProGet搭建一个内部的Nuget服务器
  3. HTTP 错误 404 - 文件或目录未找到 - 最终解决方法
  4. webpack进阶之插件篇
  5. java excel 电话号码_数值或者电话号码被EXCEL转成了科学计数法,用XSSFCell 如何读取...
  6. 继承扩展DataGrid的editors
  7. rest教程_REST保证教程
  8. js request 应用举例
  9. Linux安装GCC方法—简洁版
  10. php源码旅行网站模板,背包客旅行扁平网站模板
  11. background 与 background-image
  12. Chrome无法安装axure插件
  13. Unity/Animator -- 创建Animator Controller
  14. c 超文本标记语言,网页制作的超文本标记语言称为() - 问答库
  15. 我又有一位程序员朋友成了自由职业者
  16. 电商项目抢购压测实例
  17. 【miscellaneous】监狱安防系统智能视频监控系统设计方案
  18. GitHub也能CI/CD了 如何使用GitHub的Action?
  19. andt-pro:搭建一个ant-design-pro项目(模板)
  20. undefined symbol: _ZN6caffe26detail36_typeMetaDataInstance_preallocated_7E解决办法

热门文章

  1. linux如何扩展根分区,linux扩展根分区详细步骤
  2. 怎样使用计算机上的高级共享设置密码,win7系统电脑,如何设置共享,共享计算机的用户名和密码怎样设置...
  3. 似然函数代码c语言,从似然函数到EM算法(附代码实现)
  4. Spring学习3之IOC创建对象的方式
  5. 【Linux】14.ubuntu忘记root密码、用户密码输入次数过多锁住的问题
  6. MySQL 优化之 index merge(索引合并)
  7. Java 源码学习系列(三)——Integer
  8. Java动态编程初探——Javassist
  9. Chrome神器Vimium快捷键学习记录
  10. 虽然不能取代排版之王,但 Word 也能这样优雅地用