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

说在,开篇之前

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是针对指针(引用)而言的,它是引用类型变量的专属概念,表示一个引用类型变量声明但未初始化的状态,例如:

<span style="color:black"><span style="color:black">            <span style="color:#0000ff">object</span> obj = <span style="color:#0000ff">null</span>;</span></span>

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

object obj和object obj = null的区别?

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

<span style="color:black"><span style="color:black">            <span style="color:#008000">// Copyright   : www.anytao.com        </span></span></span>
            // Author      : Anytao,http://www.anytao.com        
<span style="color:black"><span style="color:black">            <span style="color:#008000">// Release     : 2008/07/31 1.0</span></span></span>
 
<span style="color:black"><span style="color:black">            <span style="color:#008000">//编译器检测错误:使用未赋值变量obj</span></span></span>
            //object obj;
<span style="color:black"><span style="color:black"> </span></span>
            //编译器理解为执行了初始化操作,所以不引发编译时错误
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">object</span> obj = <span style="color:#0000ff">null</span>;</span></span>
            
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">if</span> (obj == <span style="color:#0000ff">null</span>)</span></span>
            {
<span style="color:black"><span style="color:black">                <span style="color:#008000">//运行时抛出NullReferenceException异常</span></span></span>
                Console.WriteLine(obj.ToString());
<span style="color:black"><span style="color:black">            }</span></span>

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

<span style="color:black"><span style="color:black">        <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> Main(<span style="color:#0000ff">string</span>[] args)</span></span>
        {
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">object</span> o;</span></span>
            object obj = null;
<span style="color:black"><span style="color:black">        }</span></span>

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

<span style="color:black"><span style="color:black">.method <span style="color:#0000ff">private</span> hidebysig <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> Main(<span style="color:#0000ff">string</span>[] args) cil managed</span></span>
{
<span style="color:black"><span style="color:black">    .entrypoint</span></span>
    .maxstack 1
<span style="color:black"><span style="color:black">    .locals init (</span></span>
        [0] object o,
<span style="color:black"><span style="color:black">        [1] <span style="color:#0000ff">object</span> obj)</span></span>
    L_0000: nop 
<span style="color:black"><span style="color:black">    L_0001: ldnull </span></span>
    L_0002: stloc.1 
<span style="color:black"><span style="color:black">    L_0003: ret </span></span>
}

前者没有发生任何附加操作;而后者通过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操作将在编译器抛出错误提示,例如:

<span style="color:black"><span style="color:black">            <span style="color:#008000">//抛出编译时错误</span></span></span>
            int i = null;  
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">if</span> (i == <span style="color:#0000ff">null</span>)</span></span>
            {
<span style="color:black"><span style="color:black">                Console.WriteLine(<span style="color:#006080">"i is null."</span>);</span></span>
            }

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

<span style="color:black"><span style="color:black">            <span style="color:#008000">//Nullable<T>解决了这一问题</span></span></span>
            int? i = null;
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">if</span> (i == <span style="color:#0000ff">null</span>)</span></span>
            {
<span style="color:black"><span style="color:black">                Console.WriteLine(<span style="color:#006080">"i is null."</span>);</span></span>
            }

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

<span style="color:black"><span style="color:black">            <span style="color:#0000ff">int</span>? i = <span style="color:#0000ff">null</span>;</span></span>
            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的本质呢?当你声明一个:

<span style="color:black"><span style="color:black">            Nullable<Int32> count = <span style="color:#0000ff">new</span> Nullable<Int32>();</span></span>

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

<span style="color:black"><span style="color:black">    <span style="color:#0000ff">public</span> <span style="color:#0000ff">struct</span> Nullable<T> <span style="color:#0000ff">where</span> T : <span style="color:#0000ff">struct</span></span></span>
    {
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">private</span> <span style="color:#0000ff">bool</span> hasValue;</span></span>
        internal T value;
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> Nullable(T <span style="color:#0000ff">value</span>);</span></span>
        public bool HasValue { get; }
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> T Value { get; }</span></span>
        public T GetValueOrDefault();
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> T GetValueOrDefault(T defaultValue);</span></span>
        public override bool Equals(object other);
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">override</span> <span style="color:#0000ff">int</span> GetHashCode();</span></span>
        public override string ToString();
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">implicit</span> <span style="color:#0000ff">operator</span> T?(T <span style="color:#0000ff">value</span>);</span></span>
        public static explicit operator T(T? value);
<span style="color:black"><span style="color:black">    }</span></span>

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

<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> Nullable(T <span style="color:#0000ff">value</span>)</span></span>
        {
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">this</span>.<span style="color:#0000ff">value</span> = <span style="color:#0000ff">value</span>;</span></span>
            this.hasValue = true;
<span style="color:black"><span style="color:black">        }</span></span>

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

<span style="color:black"><span style="color:black">    .method <span style="color:#0000ff">private</span> hidebysig <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> Main() cil managed</span></span>
    {
<span style="color:black"><span style="color:black">        .entrypoint</span></span>
        .maxstack 2
<span style="color:black"><span style="color:black">        .locals init (</span></span>
            [0] valuetype [mscorlib]System.Nullable`1<int32> a)
<span style="color:black"><span style="color:black">        L_0000: nop </span></span>
        L_0001: ldloca.s a
<span style="color:black"><span style="color:black">        L_0003: ldc.i4 0x3e8</span></span>
        L_0008: call instance void [mscorlib]System.Nullable`1<int32>::.ctor(!0)
<span style="color:black"><span style="color:black">        L_000d: nop </span></span>
        L_000e: ret 
<span style="color:black"><span style="color:black">    }</span></span>

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

  • 可空类型表示值为null的值类型。
  • 不允许使用嵌套的可空类型,例如Nullable<Nullable<T>> 。
  • Nullable<T>和T?是等效的。
  • 对可空类型执行GetType方法,将返回类型T,而不是Nullable<T>。
  • c#允许在可空类型上执行转换和转型,例如:
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">int</span>? a = 100;</span></span>
            Int32 b = (Int32)a;
<span style="color:black"><span style="color:black">            a = <span style="color:#0000ff">null</span>;</span></span>
  • 同时为了更好的将可空类型于原有的类型系统进行兼容,CLR提供了对可空类型装箱和拆箱的支持。

 

 

 

3 ??运算符

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

<span style="color:black"><span style="color:black">            <span style="color:#0000ff">object</span> obj = <span style="color:#0000ff">new</span> <span style="color:#0000ff">object</span>();</span></span>
 
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">string</span> objName = <span style="color:#0000ff">string</span>.Empty;</span></span>
            if (obj != null)
<span style="color:black"><span style="color:black">            {</span></span>
                objName = obj.ToString();
<span style="color:black"><span style="color:black">            }</span></span>
 
<span style="color:black"><span style="color:black">            Console.WriteLine(objName);</span></span>

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

<span style="color:black"><span style="color:black">            <span style="color:#0000ff">object</span> obj = <span style="color:#0000ff">new</span> <span style="color:#0000ff">object</span>();</span></span>
            string objName = obj == null ? string.Empty : obj.ToString();
<span style="color:black"><span style="color:black">            Console.WriteLine(objName);</span></span>

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

<span style="color:black"><span style="color:black">            <span style="color:#008000">// Copyright   : www.anytao.com        </span></span></span>
            // Author      : Anytao,http://www.anytao.com        
<span style="color:black"><span style="color:black">            <span style="color:#008000">// Release     : 2008/07/31 1.0</span></span></span>
 
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">object</span> obj = <span style="color:#0000ff">null</span>;</span></span>
            string objName = (obj ?? string.Empty).ToString();
<span style="color:black"><span style="color:black">            Console.WriteLine(objName);</span></span>

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

??运算符,又称为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需要在系统中进行操作,那么典型的操作方式是:

<span style="color:black"><span style="color:black"><span style="color:black">            <span style="color:#0000ff">if</span> (user != <span style="color:#0000ff">null</span>)</span></span></span>
            {
<span style="color:black"><span style="color:black"><span style="color:black">                manager.SendMessage(user);</span></span></span>
            }

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

<span style="color:black"><span style="color:black"><span style="color:black">    <span style="color:#008000">// Copyright   : www.anytao.com        </span></span></span></span>
    // Author      : Anytao,http://www.anytao.com        
<span style="color:black"><span style="color:black"><span style="color:black">    <span style="color:#008000">// Release     : 2008/07/31 1.0</span></span></span></span>
 
<span style="color:black"><span style="color:black"><span style="color:black">    <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> NullUser : IUser</span></span></span>
    {
<span style="color:black"><span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> Login()</span></span></span>
        {
<span style="color:black"><span style="color:black"><span style="color:black">            <span style="color:#008000">//不做任何处理</span></span></span></span>
        }
<span style="color:black"><span style="color:black"><span style="color:black"> </span></span></span>
        public void GetInfo() { }
<span style="color:black"><span style="color:black"><span style="color:black"> </span></span></span>
        public bool IsNull
<span style="color:black"><span style="color:black"><span style="color:black">        {</span></span></span>
            get { return true; }
<span style="color:black"><span style="color:black"><span style="color:black">        }</span></span></span>
    }

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属性的接口,其定义为:

<span style="color:black"><span style="color:black">    <span style="color:#0000ff">public</span> <span style="color:#0000ff">interface</span> INullable</span></span>
    {
<span style="color:black"><span style="color:black">        <span style="color:#008000">// Properties</span></span></span>
        bool IsNull { get; }
<span style="color:black"><span style="color:black">    }</span></span>

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

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

<span style="color:black"><span style="color:black">    <span style="color:#008000">// Copyright   : www.anytao.com        </span></span></span>
    // Author      : Anytao,http://www.anytao.com        
<span style="color:black"><span style="color:black">    <span style="color:#008000">// Release     : 2008/07/31 1.0</span></span></span>
 
<span style="color:black"><span style="color:black">    <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> User : IUser</span></span>
    {
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> Login()</span></span>
        {
<span style="color:black"><span style="color:black">            Console.WriteLine(<span style="color:#006080">"User Login now."</span>);</span></span>
        }
<span style="color:black"><span style="color:black"> </span></span>
        public void GetInfo()
<span style="color:black"><span style="color:black">        {</span></span>
            Console.WriteLine("User Logout now.");
<span style="color:black"><span style="color:black">        }</span></span>
 
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">bool</span> IsNull</span></span>
        {
<span style="color:black"><span style="color:black">            get { <span style="color:#0000ff">return</span> <span style="color:#0000ff">false</span>; }</span></span>
        }
<span style="color:black"><span style="color:black">    }</span></span>

而对应的NullUser,其定义为:

<span style="color:black"><span style="color:black">    <span style="color:#008000">// Copyright   : www.anytao.com        </span></span></span>
    // Author      : Anytao,http://www.anytao.com        
<span style="color:black"><span style="color:black">    <span style="color:#008000">// Release     : 2008/07/31 1.0</span></span></span>
 
<span style="color:black"><span style="color:black">    <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> NullUser : IUser</span></span>
    {
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> Login()</span></span>
        {
<span style="color:black"><span style="color:black">            <span style="color:#008000">//不做任何处理</span></span></span>
        }
<span style="color:black"><span style="color:black"> </span></span>
        public void GetInfo() { }
<span style="color:black"><span style="color:black"> </span></span>
        public bool IsNull
<span style="color:black"><span style="color:black">        {</span></span>
            get { return true; }
<span style="color:black"><span style="color:black">        }</span></span>
    }

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

<span style="color:black"><span style="color:black">    <span style="color:#008000">// Copyright   : www.anytao.com        </span></span></span>
    // Author      : Anytao,http://www.anytao.com        
<span style="color:black"><span style="color:black">    <span style="color:#008000">// Release     : 2008/07/31 1.0</span></span></span>
 
<span style="color:black"><span style="color:black">    <span style="color:#0000ff">class</span> UserManager</span></span>
    {
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">private</span> IUser user = <span style="color:#0000ff">new</span> User();</span></span>
 
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> IUser User</span></span>
        {
<span style="color:black"><span style="color:black">            get { <span style="color:#0000ff">return</span> user; }</span></span>
            set
<span style="color:black"><span style="color:black">            {</span></span>
                user = value ?? new NullUser();
<span style="color:black"><span style="color:black">            }</span></span>
        }
<span style="color:black"><span style="color:black">    }</span></span>

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

<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> Main()</span></span>
        {
<span style="color:black"><span style="color:black">            UserManager manager = <span style="color:#0000ff">new</span> UserManager();</span></span>
            //强制为null
<span style="color:black"><span style="color:black">            manager.User = <span style="color:#0000ff">null</span>;</span></span>
            //执行正常
<span style="color:black"><span style="color:black">            manager.User.Login();</span></span>
 
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">if</span> (manager.User.IsNull)</span></span>
            {
<span style="color:black"><span style="color:black">                Console.WriteLine(<span style="color:#006080">"用户不存在,请检查。"</span>);</span></span>
            }
<span style="color:black"><span style="color:black">        }</span></span>

通过强制将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接口的方法而采用更简单的实现方式。重新构造一个实现相同功能的扩展方法,例如:

<span style="color:black"><span style="color:black">    <span style="color:#008000">// Copyright   : www.anytao.com        </span></span></span>
    // Author      : Anytao,http://www.anytao.com        
<span style="color:black"><span style="color:black">    <span style="color:#008000">// Release     : 2008/07/31 1.0</span></span></span>
 
<span style="color:black"><span style="color:black">    <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">class</span> UserExtension</span></span>
    {
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">bool</span> IsNull(<span style="color:#0000ff">this</span> User user)</span></span>
        {
<span style="color:black"><span style="color:black">            <span style="color:#0000ff">return</span> <span style="color:#0000ff">null</span> == user;</span></span>
        }
<span style="color:black"><span style="color:black">    }</span></span>
当然,这只是一个简单的思路,仅仅将对null value的判断转换为null object的判断角度来看,扩展方法带来了更有效的、更简洁的表现力。 

null object模式的小结

  • 有效解决对象为空的情况,为值为null提供可靠保证。
  • 保证能够返回有效的默认值,例如在一个IList<User> userList中,能够保证任何情况下都有有效值返回,可以保证对userList操作的有效性,例如:
<span style="color:black"><span style="color:black">        <span style="color:#008000">// Copyright   : www.anytao.com        </span></span></span>
        // Author      : Anytao,http://www.anytao.com        
<span style="color:black"><span style="color:black">        <span style="color:#008000">// Release     : 2008/07/31 1.0</span></span></span>
 
<span style="color:black"><span style="color:black">        <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> SendMessageAll(List<User> userList)</span></span>
        {
<span style="color:black"><span style="color:black">            <span style="color:#008000">//不需要对userList进行null判断</span></span></span>
            foreach (User user in userList)
<span style="color:black"><span style="color:black">            {</span></span>
                user.SendMessage(); 
<span style="color:black"><span style="color:black">            }</span></span>
        }
  • 提供统一判定的IsNull属性。可以通过实现INullable接口,也可以通过Extension Method实现IsNull判定方法。
  • null object要保持原object的所有成员的不变性,所以我们常常将其实现为Sigleton模式。
  • Scott Doman说“在执行方法时返回null object而不是null值,可以避免NullReferenceExecption异常的发生”,这完全是对的。

5 结论

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

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

 

Anytao | 2008-07-31 | 你必须知道的.NET

http://www.anytao.com/  | Blog: http://anytao.cnblogs.com/ | Anytao创作品,转贴请注明作者和出处,留此信息。

 

参考文献

 

(Book)Martin Fowler,Refactoring: Improving the Design of Existing Code

(cnblogs)zhuweisky,使用Null Object设计模式

(blogs)Scott Dorman,Null Object pattern

 

 

 

 

温故知新

[开篇有益] 
[第一回:恩怨情仇:is和as] 
[第二回:对抽象编程:接口和抽象类] 
[第三回:历史纠葛:特性和属性] 
[第四回:后来居上:class和struct] 
[第五回:深入浅出关键字---把new说透] 
[第六回:深入浅出关键字---base和this] 
[第七回:品味类型---从通用类型系统开始] 
[第八回:品味类型---值类型与引用类型(上)-内存有理] 
[第九回:品味类型---值类型与引用类型(中)-规则无边] 
[第十回:品味类型---值类型与引用类型(下)-应用征途] 
[第十一回:参数之惑---传递的艺术(上)] 
[第十二回:参数之惑---传递的艺术(下)] 
[第十三回:从Hello, world开始认识IL] 
[第十四回:认识IL代码---从开始到现在] 
[第十五回:继承本质论] 
[第十六回:深入浅出关键字---using全接触] 
[第十七回:貌合神离:覆写和重载] 
[第十八回:对象创建始末(上)] 
[第十九回:对象创建始末(下)] 
[第二十回:学习方法论]

 

评论列表

  

#7楼 2008-07-31 08:38 荒芜

对于NullObject Pattern应该不会大量用到把. 
我怎么感觉给类实现个INullable,有点不需要! 
或者说我们判断....== null,未尝不可! 
呵呵! 

支持(0) 反对(0)

  

#11楼 2008-07-31 08:45 菜菜灰

?? .NET 2.0也存在的吧,不是在.net 3.0才有的吧

支持(0) 反对(0)

  

#12楼 2008-07-31 08:48 横刀天笑

@荒芜 
对于一个对自己的代码追求完美的程序员来说null object pattern非常漂亮。如果不使用null object pattern,那么代码中到处会充斥着这样的代码: 
if(a != null) 

a.DoSomething(); 

注意,是到处,并不是一个地方出现。 
还有别的地方的用处: 
第一个:测试的时候,测试驱动开发,以测试先行。比如一个Blog对象,你要对Blog进行测试先行,那么你可以做一个IBlog接口,然后先实现一个“假的”InvalidBlog对象,进行测试。慢慢的添加功能,最后迭代到你要实现的Blog。 
第二个:比如做用户验证的时候吧,你可以做一个IUser接口,一个InvalidUser,一个ValidUser,当验证失败返回InvalidUser,验证成功返回ValidUser。而调用验证这一层的客户代码只需要面对IUser这个接口就可以了。 

上面的可能和nullobject pattern的初衷有点背离,不过也是nullobject pattern的演绎。看你怎么用了,我觉得nullobject pattern简单而优美

支持(0) 反对(0)

  

#30楼 2008-07-31 10:05 helloworld22

public void SendMessageAll(List<User> userList)

{

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

foreach (User user in userList)

{

user.SendMessage(); 

}

}


但是user.SendMessage()之前,set user = null还是会报错吧。
public void SendMessageAll(List<User> userList)

{

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

foreach (User user in userList)

{

user = null;
user.SendMessage(); 

}

}

支持(0) 反对(0)

  

#32楼 2008-07-31 10:08 primeli

问下啊,String s = null 与 String s = String.Empty 和 String s = "" 这3个有什么不同?

支持(0) 反对(0)

  

#33楼 2008-07-31 10:19 阿德斯基

--引用-------------------------------------------------- 
菜菜灰: ?? .NET 2.0也存在的吧,不是在.net 3.0才有的吧 
-------------------------------------------------------- 
我测试了下 果然在.net2.0下成立 本人不才 头一回知道.net2.0下可以这样用 一直用一个耳朵的,今天长见识了

支持(0) 反对(0)

  

#34楼 2008-07-31 10:28 妖居

全面!精辟! 

建议楼主再讲讲DBNull和null的关系。我为了做CRM系统特意写了个DBNullable<T>类来处理DBNull和null的问题。不知道有没有更好的方法或者.NET已经提供了。 

另外借地方说个VB中关于null的问题。IIf函数允许VB像C#一样使用三元运算符 ? : 第一个参数是一个返回Boolean的判断,然后两个参数分别是为True和False的时候的返回值。但是和C#不同他的后两个参数是优先运算的。所以如果用IIf(user Is Nothing, String.Empty, user.ToString())则会出现运行时异常。

支持(0) 反对(0)

  

#35楼 2008-07-31 10:29 Angel Lucifer

在我看来,Nulll Object模式完全属于设计过度,也没看出有多优雅。
如果你的项目使用类似的思想,那可要质疑你的程序性能了。

支持(0) 反对(0)

  

#36楼 2008-07-31 10:37 Angel Lucifer

@非主流程序员
null 的含义,楼主已经讲的很清楚了。
剩下的

String.Empty == "",呵呵。

支持(0) 反对(0)

    

#38楼 2008-07-31 11:15 Duron800

--引用-------------------------------------------------- 
Angel Lucifer: @非主流程序员 
null 的含义,楼主已经讲的很清楚了。 
剩下的 

String.Empty == &quot;&quot;,呵呵。 
-------------------------------------------------------- 
string.Empty好像不会提示"never used"

支持(0) 反对(0)

  

#39楼 2008-07-31 11:16 Klesh Wong

这里的Null Object与直接判断null优雅在哪里?
不过是把 object == null 这个判断包装在另一个方法而已,除了罗索和多一层调用看不出有什么优势啊。
Null Object不是这么用的吧?不如举一下Logging的例子更好,
Null Logging看起来就合理多了。

支持(0) 反对(0)

  

#40楼 2008-07-31 11:38 横刀天笑

@Angel Lucifer 
@Klesh Wong 
嗯,我也觉得LZ这里的NullObject Pattern的例子没有选好,这样写实在是有点设计过度,不过NullObject Pattern确实很有用的,null object不仅仅是一个"null"对象,还可以理解为无效对象或者非法对象,也就是表示一种对象状态,所以程序里有这样的情况可以使用NullObject Pattern。 

支持(0) 反对(0)

  

#41楼 2008-07-31 11:52 怪怪

晕倒, 原来你还在在意那天说的人家传过来的玩意是不是null的问题。

NullObject不见得一定就是设计过度, 不过也不见得处处使用。 比如我自己, if(null == xx || xx.length == 0)这种代码已经打顺手了 -___-

如果嫌它丑, 可以写一个静态的Guard类,然后Guard.IsNull(obj),Guard.IsNullOrEmpty(obj)这样子。 主要是这些写法是一个明确的表达。

而NullObject尤其是你最后的那种Extension,看起来代码虽然“优雅”,但是在一些情况下, 会把必要的信息隐藏了, 从而存在不易察觉的隐患。

支持(0) 反对(0)

  

#42楼 2008-07-31 12:00 Klesh Wong

@横刀天笑
是这样子的,Null Object当然是相当滴有用,就比如说Logging,健壮的程序很多地方都是需要Logging的,但在某些时候可能又不需要进行logging,在每一次地方使用null判断是非常罗索的事情,所以一个NullLogger会很有用,NullLogger实行ILogger的行为但其实不做任何logging,这就是NullObject的美妙之处——使程序变得简洁明了。
但LZ的例子则恰好相反,不但没有使程序更加简洁,反而变得罗索。。。

支持(0) 反对(0)

  

#43楼 2008-07-31 12:03 菜菜灰

回楼上,2.0本身就提供这样一个方法 

String.IsNullOrEmpty(string value)

支持(0) 反对(0)

  

#44楼 2008-07-31 12:08 怪怪

@菜菜灰
问题是你要防止NullOrEmpty的类型, 不只string一种~。

支持(0) 反对(0)

  1

  

#46楼 2008-07-31 13:07 Klesh Wong

@怪怪
那根本就不优雅,原则上来说user.IsNull根本就违反直觉了吧。
如果user不存在则返回null或者直接抛出异常我想都是大家可以接受的行为。
相反,你返回了一个非null的实例,是不是就是暗示了user就是存在的,最后还要调用它的"IsNull"来判定它本身是不是null,这个逻辑上好像挺绕。就好像在问一个不存在的人“你存不存在”这样子。
静态类的判断也是很好的,没有逻辑上的问题。就好像你问一个第三者这个人存不存在。直接判断null就相当于自己看看那个人存不存在。

支持(0) 反对(0)

  

#47楼 [楼主] 2008-07-31 13:55 Anytao

@荒芜 
INullable只是在设计层面,提供一个统一的契约,由实现null object的类来遵守;而关于以obj == null方式进行判断,很多时候还是无可避免,因为你不可能给系统中所有的类实现相应的NullObject对应类,同时很多临时变量还是无可避免的回到obj == null 

#51楼 
[楼主] 2008-07-31 14:08 Anytao
@菜菜灰 
应该是2.0,和Nullable<T>一起问世的,我得再查查出处。谢谢提醒
支持(0) 反对(0)
#52楼 [楼主] 2008-07-31 14:11 Anytao
@BZZ 
呵呵,还行啦 
支持(0) 反对(0)
#53楼 [楼主] 2008-07-31 14:16 Anytao
@戏水 
we are always nullable . 
这句话,我喜欢。 

关于null的理解,其实也不需要该深奥,但是它确实就是“空”这个意思,不知道是不是和“色既是空”有异曲同工之妙,谁知道呢?
支持(0) 反对(0)
#54楼 [楼主] 2008-07-31 14:16 Anytao
@侯垒 
呵呵,欢迎兄弟
支持(0) 反对(0)
#55楼 [楼主] 2008-07-31 14:17 Anytao
@蛙蛙池塘 
谢谢啦,感动的。。
支持(0) 反对(0)
#56楼 [楼主] 2008-07-31 14:17 Anytao
@真见 

Thanks great 
 
支持(0) 反对(0)
#57楼 [楼主] 2008-07-31 14:18 Anytao
@jillzhang 
呵呵,眼泪哗哗的
支持(0) 反对(0)
#58楼 [楼主] 2008-07-31 14:20 Anytao
@Artech 
好久没有发帖了,今天先打个头阵,不过还没有什么大计划,有了马上偷偷告诉你:-) 

Null = 0(二进制意义) 

Nice...
支持(0) 反对(0)
#59楼 2008-07-31 15:12 信110
object o; object o = null;
区别是第一个CLR为其赋默认NULL,第二个CLR先为其赋默认NULL,程序再显式赋为NULL,最终效果没有区别。C#中所有变量使用前必须先赋值--这个赋值指的是程序的显式赋值,所以第一个Write(o)是不行的,第二个可以;但在IL中两个都可以:
.locals init (object v1)
ldloc.0
call void [mscorlib]System.Console::Write(object)
------------------------------------------------
.locals init (object v1)
ldnull
stloc.0
ldloc.0
call void [mscorlib]System.Console::Write(object)
因为有init,所以两个o在进入方法前都被CLR赋NULL了(二进制的0)。

值类型如何呢?
int i; int i = 0;
在C#中,区别是明显的:前者未赋值(不能使),后者赋值了(能使)。在IL中:
.locals init (int32 v1)
ldloc.0
call void [mscorlib]System.Console::Write(int32)
------------------------------------------
.locals init (int32 v1)
ldc.i4.0
stloc.0
ldloc.0
call void [mscorlib]System.Console::Write(int32)
--------------两个输出都是0(可见第一个i CLR是给赋了值的)
把第一个改改(去掉init):
.locals (int32 v1)
ldloc.0
call void [mscorlib]System.Console::Write(int32)
-----------------输出:10956832
是不是说明LCR没有给i赋值?,而是让它随便是什么。

有没有区别我也不清楚了
以上为个人理解,不对请指出-_-
支持(0) 反对(0)
#60楼 2008-07-31 16:13 999999999999999
很好,好久不见你的文章了
支持(0) 反对(0)
#61楼 [楼主] 2008-07-31 16:19 Anytao
@姜敏 
呵呵,其实还有很多没有写,例如Linq中的Null,SQL中的Null,只是限于精力没有继续,以后吧:-)
支持(0) 反对(0)
#62楼 [楼主] 2008-07-31 16:20 Anytao
@丁学 
@横刀天笑 

--引用-------------------------------------------------- 
横刀天笑: @丁学 
啥都没有就未知了,俗话说武功最高境界就是无招,见招拆招,什么招都没有你还拆什么? 呵呵,开个玩笑 
-------------------------------------------------------- 

嘿嘿,都有道理。
支持(0) 反对(0)
#63楼 [楼主] 2008-07-31 16:21 Anytao
@Duron800 
好,欢迎回来看看:-)
支持(0) 反对(0)
#64楼 2008-07-31 22:56 波波塔
??这个运算符.我第一次看到.可能就是两周前吧,当时看MSDN里面的例子突然看到的.当时还以为是MSDN打错了....后来查了下帮助,第一眼看懂了它的大概用途,再看一眼MSDN,傻眼了...后面说的好像和前面的不一样.... 
不知道有没有筒子和俺一样的哈.嘎嘎 

另外楼主的那个 null object 例子,不是要对相应的非null object 实现空的方法体吗?那不是更麻烦?
支持(0) 反对(0)
#65楼 [楼主] 2008-08-01 13:18 Anytao
@Tony Zhou 

public void SendMessageAll(List<IUser> userList) 



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

foreach (IUser user in userList) 



user = null; 
user.SendMessage(); 





Is OK.
支持(0) 反对(0)
#66楼 [楼主] 2008-08-01 13:19 Anytao
@在职研究生 
呵呵,那是我的荣幸:-)
支持(0) 反对(0)
#67楼 2008-08-01 13:22 陈晨
谢谢,收益了
支持(0) 反对(0)
#68楼 [楼主] 2008-08-01 13:30 Anytao
--引用-------------------------------------------------- 
非主流程序员: 问下啊,String s = null 与 String s = String.Empty 和 String s = "" 这3个有什么不同? 
-------------------------------------------------------- 
@非主流程序员 

string s = null和string.Empty的区别是前者在内存中保持的是一个空引用,而后者则有实实在在的指向,只是"值"为空而已,对前者进行操作会引发NullReferenceException,而后者不会。 

String s = String.Empty 和 String s = ""很多时候是可以互换的,但是更推荐用String.Empty
支持(0) 反对(0)
#69楼 [楼主] 2008-08-01 13:31 Anytao
@探索之鸟 
呵呵,也是我的一个错误
支持(0) 反对(0)
#70楼 [楼主] 2008-08-03 20:51 Anytao
@妖居 
呵呵,首先VB.NET我不是很清楚,所以关于Iif没有什么发言权啦:-) 

本来本文最初想要涉及DBNull和Null in LINQ的,但是最后限于时间和精力就没有成行。DBNull是.NET中预定义的处理数据库值为空的独立类型,同时提供了很多有用的方法来支持其操作,有机会可以在写个续什么的:-)
支持(0) 反对(0)
#71楼 [楼主] 2008-08-03 21:35 Anytao
@Angel Lucifer 

如果综合来看全文关于null的讲述,我不知道所谓的过度体现在何处,当然以obj == null完全可以实现你所谓的非过度,但是正如文中所提null object旨在解决一定的问题,针对具体的需求而言。 
另外,关于性能的质疑,我觉得大可不必,因为其实很多设计都是以牺牲稍微的性能为代价的,不然什么三层架构还有什么意义,只有一层岂不更性能更高。 

:-)
支持(0) 反对(0)
#72楼 [楼主] 2008-08-03 21:42 Anytao
@Klesh Wong 
关于这个示例,我会考虑你的意见,不过关于IsNull的判断,其实在示例中我已经给出了应用,你完全可以不必进行IsNull或者user == null这样的判断,而直接应用: 

public static void Main() 

UserManager manager = new UserManager(); 
//强制为null 
manager.User = null; 
//执行正常 
manager.User.Login(); 


所以关于文中示例,可以全面的了解,只关注: 
if (manager.User.IsNull) 

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

是没有意义的。
支持(0) 反对(0)
#73楼 [楼主] 2008-08-03 21:45 Anytao
@横刀天笑 
很同意老兄的想法,关于User实例,我再看考虑考虑大家的意见,不过正如我回复Klesh Wong的留言一样,希望更多关注的是执行层面对于null object解决问题的分析情况。
支持(0) 反对(0)
#74楼 [楼主] 2008-08-03 21:51 Anytao
@怪怪 
嘿嘿,确实是那天的问题让我有时间思考这个话题,所以进行一点小小的探讨。 

言归正题,关于null object,我的想法并不局限于设计的过度和编码的习惯,其实null object pattern有其自身解决问题的应用领域,这点在文中其实不可避免,但是很多时候你不可为所有的0bj == null判断提供对应的null object类,使其在null决议时执行值为null时的业务逻辑,因为可能我的obj本身就是一个临时变量。 

最后,关于Extesion的解决思路,呵呵,我没有考虑太多“隐藏信息”,只是当作一个小技巧在这里玩玩:-)
支持(0) 反对(0)
#75楼 [楼主] 2008-08-03 22:02 Anytao
@Klesh Wong 



首先,关于user.IsNull是否违反直觉,我觉得根本就没有什么直接而言,正像怪怪所言,更多的体现为一个习惯,编码的习惯和直觉没有关系。



其次,正如你反复以Logging举例,我认为“什么对象”可以抽象为null object其实决定于具体的需求,回到我的User实例。同样的道理,健壮的程序很多地方也需要User的状态判断,例如Login、Logout等等,具体有什么样的需求决定于具体的业务逻辑,这里只是一个简单的示例。因此,同样的每个地方都进行null判断也是很啰嗦的,所以以NullUser来实现对null判别的对象级判断可以有更好的代码体验,和系统健壮性:如果用户存在,执行User.Login()登陆合法系统,如果用户不存在,执行NullUser.Login()转到错误页码或者其他的非法登陆处理逻辑。 



而不管是User存在还是不存在,在客户处理端是想到简洁和优雅: 

public static void Main() 



UserManager manager = new UserManager(); 

//强制为null 

manager.User = null; 

//执行正常 

manager.User.Login(); 





没有任何IsNull或者user == null这样的判断,同时能够保证系统执行的可靠性和健壮性。 



和Logging是否是异曲同工??
支持(0) 反对(0)
#76楼 [楼主] 2008-08-03 22:05 Anytao
@菜菜灰 
看文知意,null object和string.IsNullOrEmpty是两回事儿,虽然有某种联系:-)
支持(0) 反对(0)
#77楼 [楼主] 2008-08-03 22:06 Anytao
@横刀天笑 
这两天太忙了,回复有点儿不及时,不像我的风格。 
哈哈,随时沟通:-)
支持(0) 反对(0)
#78楼 [楼主] 2008-08-03 22:08 Anytao
@信110 
很感谢你的详细分析,呵呵,和我的想法差不多,不过在IL层还是不能100%的看出二者的差别,不过我坚持代码优化之后在本质上二者是相同的。
支持(0) 反对(0)
#79楼 [楼主] 2008-08-03 22:09 Anytao
@北京奥运 
呵呵,有空就好好写写想法,其乐无穷:-)
支持(0) 反对(0)
#80楼 [楼主] 2008-08-03 22:11 Anytao
@airwolf2026 
呵呵,麻烦是为了换取优雅,就像工厂方法模式,为了创建对象还不是得付出附加的代码操作,这是无可避免的代价:-) 

不过,在整个系统中,你可以有效避免isNull或者user == null这样的判断,换来了简洁和健壮:-)
支持(0) 反对(0)
#81楼 [楼主] 2008-08-03 22:11 Anytao
@陈晨 
哈哈,一样一样:-)
支持(0) 反对(0)
#82楼 2008-08-05 11:19 Klesh Wong
@Anytao
User是否Login和表现层上下文有关,跳转这些更加和具体表现层相关,原则上User实体是不应该关心这些层次。Login/Logout这些状态应该由表现层的类(如IPrincipal)包装User才是,这也是普遍做法。
NullObject应该是可以完全替代原类/接口的Dummy Instance,也就是当客户端使用这个NullObject的时候,应该是像使用一个类那样,一个类怎么还会有IsNull这个的判定。它既然是实例怎么能判断自己是不是null。最重要的一点就是NullObject的使用者就是假定了得到的永远是实例,而不会是null。
编程的习惯和直觉不是没有关系,要是不喜欢直觉这个词,用习惯来代替我所说的直觉也是一样的。
当你给某一个属性赋值后又马上调用它的方法,这也是违反了习惯。OO是模拟现实中抽象出来的逻辑的编程方法,代码的表述的逻辑有误这就是不符合OO精神的。

public static void Main()
{
UserManager manager = new UserManager();
//强制为null
manager.User = null;
//执行正常
manager.User.Login();


不可否认你的代码完全可以正常运行,但你不觉得这样可读性太差了吗?这样的代码你自己能看得懂,但别人都会一头雾水吧。完全不觉得这样的代码是优雅的呢。

这段代码固然貌似就不用判断用户是不是Null。但其它地方一定还需要判定user是不是null,比如说登录才显示某些信息之类,这样的NullObject根本就没意义了吧,之所谓使用NullObject就是它可以完全替代不是null时的情况,而User作为“业务实例”没办法做完全替代。
支持(0) 反对(0)
#83楼 2008-08-05 11:33 Klesh Wong
--引用--------------------------------------------------
Anytao: @Klesh Wong
关于这个示例,我会考虑你的意见,不过关于IsNull的判断,其实在示例中我已经给出了应用,你完全可以不必进行IsNull或者user == null这样的判断,而直接应用:

public static void Main()
{
UserManager manager = new UserManager();
//强制为null
manager.User = null;
//执行正常
manager.User.Login();
}

所以关于文中示例,可以全面的了解,只关注:
if (manager.User.IsNull)
{
Console.WriteLine(&quot;用户不存在,请检查。&quot;);
}
是没有意义的。
--------------------------------------------------------
试问能在程序中完全避免这种判断的出现吗?不能的话,关注它又怎么会没有意义呢?相反, 这种判断应该还是到处都是。
即使不看这个,还有其它许多问题,LZ把很多表现层逻辑放到实体类中也不是太好,违反SRP。
其实整篇都写得很好,只是个人认为NullObject的例子举得不太恰当而已。
??操作符也可以再讲多一个trick,可以 a ?? b ?? c ?? d ?? e 这样……
支持(0) 反对(0)
#84楼 2008-08-12 17:03 Zzx飘遥
人帅文章也帅^_^
支持(0) 反对(0)
#85楼 [楼主] 2008-08-12 20:36 Anytao
@Klesh Wong 

对于Wong兄上述描述,给我不错的启示。不过,我想自己的想法可能没有在文章中表达的特别清晰,或者并没有收到预期的效果。关于NullObject完全可以替代原类的Dummy Instance,这点是毋庸置疑的,否则就失去了NullObject本身的意义,也会违反设计原则的替换规则。 

之所以将IsNull加入,我的考虑仅仅是从提供一个Property这个观点上来看的,不可否认很多时候还是需要以IsNull这样的形式来进行判断更加方便,正像怪怪提出的习惯性问题。我完全同意将IsNull去掉会显得更加NullObject,正像老兄表达的一样:-) 

关于你提到的代码实例的种种不适,例如: 
---------------------------------- 
//强制为null 
manager.User = null; 
//执行正常 
manager.User.Login(); 
的可读性 

--------------- 
把很多表现层逻辑放到实体类中也不是太好,违反SRP 
----------- 

之所以有这样别扭的实现,完全是因为在有限的篇幅来表达一个测试,我不想花太多的代码,或者你可以将它看成伪代码,只是可以运行罢了:-)

再次感谢老兄中肯的提议,让我了解很多难能可贵的思考,欢迎常来赐教:-) 
支持(0) 反对(0)
#86楼 [楼主] 2008-08-12 20:36 Anytao
@飘遥 
哈哈,见过人吗?
支持(0) 反对(0)
#87楼 2008-08-13 10:29 Klesh Wong
@Anytao 
言重言重,既是同道中人,当可多多交流。 
兄台肯好好考虑路人甲的意见,也是难能可贵。 
望能坚持,有些人名气一大就目中无人呐,根本不会去考虑别人的批评,呵呵。
支持(0) 反对(0)
#88楼 2008-08-13 13:00 Zzx飘遥
@Anytao 
见过,植物园~
支持(0) 反对(0)
#89楼 [楼主] 2008-08-13 14:23 Anytao
@飘遥 
哈哈,我还以为此飘遥非彼飘遥,上次玩儿得很开心:-)
支持(0) 反对(0)
#90楼 2009-01-21 17:20 痘痘熊
IsNullable这个词是否适合用来做Interface的名字呢?参考系统中常用的interface:IComparable, ICloneable, IEnumerable等等,表达的都是某个本身并不一定具备的属性。而IsNullable是Class必然具备的属性,就是说Class一定是IsNuulable的,因此命名上可以再考虑考虑。 : )
支持(0) 反对(0)
#91楼 2009-01-27 22:36 刘晓飞
User.IsNull违反了OOD
支持(0) 反对(0)
#92楼 [楼主] 2009-02-02 08:51 Anytao
@痘痘熊 
接口的名称是INullable,而非IsNullable:-)
支持(0) 反对(0)
#93楼 [楼主] 2009-02-02 08:55 Anytao
@刘晓飞 
这一争论已是个难断的公论了,园子中也有不少辩论,所以没法在此多言了,对于OO而言我始终认为又是不必过度追求纯粹的100%的品质,而灵活应用有时更重要:-)
支持(0) 反对(0)
#94楼 2009-03-05 15:19 Sam Lin
我觉得null object没必要吧,虽然客户端的代码优美了,但会不会造成类爆炸呢?
支持(0) 反对(0)
#95楼 [楼主] 2009-03-05 20:53 Anytao
@Sam Lin 
既然是“会不会”,就有对应的“必要不必要”问题,权衡没有标准,此处只给出建议。
支持(0) 反对(0)
#96楼 2011-07-12 11:49 testzhangsan

引用刘晓飞:User.IsNull违反了OOD


是的,我也觉得,这会让维护变得痛苦。光一个 User.IsNull 就可以看出 User 一定不为 null 。
支持(0) 反对(0)
#97楼 2011-11-24 15:01 Patrick Yu
正文的例子放在这里看来没有体现出应有的效果,建议拿掉。不如专门开一篇讲这个更好,不然限于篇幅而产生问题太可惜了,更担心一些同学被误导而滥用这个模式。
支持(0) 反对(0)
#98楼 2014-07-11 17:04 Tony二师弟
文章提到的的Null存在即意义,我想了下这个Null Object模式的意义,文中的User.Login()的例子确实没有说服力。所以我也想说,这篇文章存在即意义。于是想到了一个例子,可以使文章更具有说服力:
伪代码:
定义:
public class Element
{
public Attribute Attr(string attrName);
}
public class Attribute
{
public string Value();
}
调用:
Attribute ele=element.Attr("Name");
var value=string.Empty;
if(ele!=null)
{
value=ele.Value();
}
引入NullObject模式后,Attr()方法可以返回一个NullAttribute,NullAttribute与文中的Login一样,可以直接调用Value(),于是代码可以这样写,
var value=element.Attr("Name").Value();
原来的四行代码变成现在的一行代码,有木有想拥抱的感觉。
只是那个Login的例子有点不恰当。

支持(0) 反对(0)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438726.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html用表格做个人主页页面,利用HTML的表格进行页面布局

在DIVCSS布局出现前&#xff0c;基本上所用的网站都使用table来进行布局。因为table布局很简单&#xff0c;但是table布局不灵活且代码很多。下面将介绍怎样使用table来进行布局。实例&#xff1a;我们来布局一个常见网站后台程序的架构。布局图如下所示&#xff1a;实例代码&a…

cesium坡度坡向分析_景观设计分析图制作技巧到底是什么?

国外设计中&#xff0c;人们都开始用动态分析图啦厉害的不要不要啊&#xff01;如果你也想做如此高逼格的分析图记得往下看&#xff01;景观设计分析为&#xff1a;人文&#xff0c;背景&#xff0c;区位&#xff0c;现状&#xff0c;历史&#xff0c;功能&#xff0c;流线&…

采购模板html5,蓝色的采购信息管理系统手机界面wap模板

手机版大气信息管理系统界面模板&#xff0c;采购信息管理wap手机模板下载。资源下载此资源下载价格为4D币&#xff0c;请先登录资源文件列表codedown123-080801-25/business_log.html , 6657codedown123-080801-25/choose.html , 6869codedown123-080801-25/css/animate.css ,…

adobe audition cs6 能打开mpcm文件吗?_Adobe全家桶出现这些漏洞,赶紧上官网下载补丁吧...

导语&#xff1a;Adobe已发布了计划的2020年7月安全更新&#xff0c;涵盖了五个不同产品领域的缺陷&#xff1a;Creative Cloud Desktop&#xff1b;媒体编码器&#xff1b;下载管理器; 真正的服务&#xff1b;和ColdFusion。其中四个错误的严重性被评为严重&#xff0c;而其他…

详解CSS的相对定位和绝对定位

CSS的相对定位和绝对定位 一、Static 静态定位 通常情况下&#xff0c;我们元素的position属性的值默认为static 就是没有定位&#xff0c;元素出现在正常的文档流中&#xff0c;这个时候你给这个元素设置的left,right,bottom,top这些偏移属性都是没有效果的&#xff0c;不会生…

观看实验中微型计算机虚拟拆装演示,虚拟仿真实验 北斗一号微机原理虚拟仿真实验系统64位 v3.0...

下面我们对虚拟仿真实验 北斗一号微机原理虚拟仿真实验系统64位 v3.0文件阐述相关使用资料和虚拟仿真实验 北斗一号微机原理虚拟仿真实验系统64位 v3.0文件的更新信息。虚拟仿真实验 北斗一号微机原理虚拟仿真实验系统64位 v3.0“微机原理虚拟仿真实验”适用于《微机原理》《微…

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

走钢丝的人&#xff0c;在刺激中体验快感。带着问题思考&#xff0c;在问题上迸发火花。 或者给问题以答案&#xff0c;或者给答案以问题&#xff0c;你可能永远无法看清全部&#xff0c;但是总能从一点突破很多。事实的关键就在于面对问题&#xff0c;我该如何思考&#xff1…

springboot超详细教程_超详细便当袋教程 || 特殊时期,自己带饭最安心!

持续受疫情影响&#xff0c;闷在家里的广大网友们早就坐不住了。尤其是最近各地复工陆续开始&#xff0c;小心心是不是开始躁动了&#xff1f;终于可以出门放飞自我&#xff0c;放肆吃吃喝喝了嘛&#xff1f;再忍一忍呀同志们&#xff01;疫情还没结束&#xff0c;病毒还没被消…

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

1 引言今天Artech兄在《关于Type Initializer和 BeforeFieldInit的问题&#xff0c;看看大家能否给出正确的解释》一文中让我们认识了一个关于类型构造器调用执行的有趣示例&#xff0c;其中也相应提出了一些关于beforefieldinit对于类型构造器调用时机的探讨&#xff0c;对于我…

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

说在&#xff0c;开篇之前很早就有说说Metadata&#xff08;元数据&#xff09;和IL&#xff08;中间语言&#xff09;的想法了&#xff0c;一直在这篇开始才算脚踏实地的对这两个阶级兄弟投去些细关怀&#xff0c;虽然来得没有《第一回&#xff1a;恩怨情仇&#xff1a;is和as…

计算机无法找到组件c0000135,电脑显示没有找到dwmapi.dll组件怎么办?计算机丢失dwmapi.dll的处理方法...

很多用户在操作Windows系统的过程中发现“没有找到dwmapi.dll”&#xff0c;如果丢失dwmapi.dll组件会导致应用程序无法运行。其实&#xff0c;大家可以尝试在相关网站下载所缺少的组件&#xff0c;或者是通过第三方软件来进行安装下载&#xff0c;这里小编带领大家看看具体解决…

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

说在&#xff0c;开篇之前书接上回[第二十四回&#xff1a;认识元数据和IL&#xff08;上&#xff09;]&#xff0c;我们对PE文件、程序集、托管模块&#xff0c;这些概念与元数据、IL的关系进行了必要的铺垫&#xff0c;同时顺便熟悉了以ILDASM工具进行反编译的基本方法认知&a…

小学计算机制作表格教案,小学信息技术《表格的制作》教案

小学信息技术《表格的制作》教案教学目标&#xff1a;知识目标&#xff1a;了解什么是网页表格能力目标&#xff1a;学会插入表格&#xff1b;掌握在表格中插入文字和图片的方法&#xff1b;学会设置单元格属性&#xff1b;掌握拆分和合并单元格。情感目标&#xff1a;通过研究…

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

说在&#xff0c;开篇之前书接上回&#xff1a; 第二十四回&#xff1a;认识元数据和IL&#xff08;上&#xff09;&#xff0c; 第二十五回&#xff1a;认识元数据和IL&#xff08;中&#xff09; 我们继续。 终于到了&#xff0c;说说元数据和IL在JIT编译时的角色了&#x…

计算机电子电路原理图,学看电路原理图入门知识积累 - 全文

一、电子电路的意义电路图是人们为了研究和工程的需要&#xff0c;用约定的符号绘制的一种表示电路结构的图形。通过电路图可以知道实际电路的情况。这样&#xff0c;我们在分析电路时&#xff0c;就不必把实物翻来覆去地琢磨&#xff0c;而只要拿着一张图纸就可以了。在设计电…

[你必须知道的.NET]第二十八回:说说Name这回事儿

1 缘起 老赵在谈表达式树的缓存&#xff08;2&#xff09;&#xff1a;由表达式树生成字符串中提到&#xff0c;在描述Type信息时讨论FullName或者AssemblyQualifiedName提供完整的Type信息&#xff0c;虽是小话题&#xff0c;但却是值得有聊的话题。在.NET中反应一个Type名称…

library的英语怎么读音_【英语角】———学习方法分享

点击蓝字 关注我们每天学习一点点单词的记忆是一件很让人头疼的事情&#xff0c;但单词又是学习英语的基石&#xff0c;非常重要。那么有什么方法能让单词记忆变得简单有效呢&#xff1f;不妨试试下面这些方法吧。1、卡片记忆自制单词卡片&#xff0c;随身带着&#xff0c;有空…

计算机作文叙事,电脑争夺战叙事作文

电脑争夺战叙事作文在生活、工作和学习中&#xff0c;大家一定都接触过作文吧&#xff0c;作文是通过文字来表达一个主题意义的记叙方法。那么你有了解过作文吗&#xff1f;下面是小编帮大家整理的电脑争夺战叙事作文&#xff0c;欢迎阅读与收藏。这是一个温暖的下午&#xff0…

[你必须知道的.NET]第二十九回:.NET十年(上)

引言 语言是程序开发者行走江湖的手上利器&#xff0c;各大门派的高手在论坛、博客为了自家门派争吵不已早是技术世界中的亮丽风景&#xff0c;虽多少为刚刚踏入江湖的新手提供了思考的素材&#xff0c;但也同时迷惑了初出茅庐的前行方向。 本文不欲计较门派的高下&#xff0…

springboot 做表白墙_华广表白墙 第六期|hsl每天都想和你嘻嘻哈哈

1回复第五期 10 没了就没了&#xff0c;不值得就要留恋219级人力4班的银发女生看见你的第一眼就觉得你是一个天使&#xff0c;你的眼睛真的把我迷住了。如果可以的话能不能加你的微信&#xff0c;谢谢?3捞一下13号(周日晚上)21.15左右在校门口益禾堂买奶茶的小姐姐 金发 牛仔…