001-认识全面的null

021-认识全面的null

null
nullable
??运算符
null object

从什么是null开始?
  1. null,一个值得尊敬的数据标识。

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

  3. 在.NET中,null表示一个对象引用是无效的。作为引用类型变量的默认值,null是针对指针(引用)而言的,他是引用类型变量的专属概念,表示一个引用类型变量声明但未初始化的状态。例子如下i。此时obj仅仅是一个保存在线程栈上的引用指针,不代表任何意义,obj未指向任何有效实例,而被默认初始化为null。
    例如:object obj = null;

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

  5. 在.NET中,对null有如下的基本规则和应用:
    a. null为引用类型变量的默认值,为引用类型的概念范畴。
    b. null不等于0,"",string.Empty
    c. 引用is或as模式对类型进行判断或转换时,需要做进一步的null判断
    d. 判断一个变量是否为null,可以应用 == 或 != 操作符来完成。
    e. 对任何值为null的变量操作,都会抛出NullReferenceException异常。

Nullable (可空类型)

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

//抛出编译时错误
int i = null;
if(i == null)
{
	Console.Write("i is null.");
}

正如上例中,很多情况下作为开发人员,我们更希望能够以统一的方法来处理,同时也希望解决实例业务需求中对“值”也可以为“空”这一实际情况的映射,因此,自.NET2.0以来,这一特权被新的System.Nullable(即:可空值类型)的诞生而打破,可以用下述方法被实现:

  //Nullable<T>解决了这一问题
  int? i = null;
  if(i == null)
  {
        Console.Write("i is null.");
  }

一定很好奇为什么上述代码中没有Nullable,因为这是C#的一个语法糖,以下代码是完全等效的。显然,我们更中意以第一种简洁而优雅的方式来实现我们的代码,但是在本质上Nullable和 T?他们是一路货色。

int? i = null;
Nullable<int>  i = null;

可空类型的伟大意义在于,通过Nullable类型,.NET为值类型添加“可空性”,例如Nullable的值就包含true ,false 和 null, 而Nullable则表示值既可以为整形也可以为null,同时,可空类型实现了统一的方式来处理值类型和引用类型的“空”值问题,例如值类型也可以享有在运行时以NullReferenceException异常来处理。
另外,可空类型是内置于CLR的,所以它并非C#的独门绝技,VB.NET中同样存在相同概念。

Nullable的本质(IL)

Nullable本质上仍是一个struct值类型,其 实例对象仍然分配在线程栈上。其中value属性封装了具体的值类型,Nullable进行初始化时,将值类型赋给value,可以从其构造函数获悉(public Nullable(T value) { this.value = value; this.hasValue = true; } )。以下时Nullable在.NET中的定义:

public struct Nullable<T> where T: struct
{
    private bool hasValue;
    internal Tvalue;
    public Nullable(T value);
    public bool HasValue{get; }
    public T value { get; }
    public T GetValueOrDefault();
    public T GetValueOrDefault(T defaultValue);
    public override bool Equal(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实现相应的Equals、ToString、GetHashCode方法,以及显式和隐式对原始值类型与可空类型的转换。因此,在本质上Nullable可以看成是预定义的struct类型,创建一个Nullable类型的IL表示可以非常清晰的提供例证,例如创建一个值为int型的可空类型过程。

可空类型小结:

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

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

object obj = new object();
string objName = string.Empty;
if(obj != null)
{
    objName = obj.ToString();
}
Console.Write(objName);

但是以上这种方法是不太好的,下面是用(? : )三元运算符:

object obj = new object();
string objName = obj == null ? string.Empty : obj.ToString();
Console.Write(objName);

上述obj可以代表任意的自定义类型对象,你可以通过覆写ToString()方法来输出你想要的输出结果,但是在.NET3.0中提供了新的操作运算符来简化null值的判断过程,就是 : ??运算符。例下:

object = null;
string objName = (obj ?? string.Empty).ToString();
Console.Write(objName);

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

Null Object模式

解决什么问题? null object模式就是为对象提供一个指定的类型,来代替对象为空的情况。说白了就是解决对象为空的情况,提供对象“什么也不做”的行为,举例,一个User类型对象user需要在系统中进行操作,那么典型的操作方式是:

if(user != null)
{
manager.SendMessage(user);
}

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

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 模式方案,并将其实现为具有.NET特殊的null object ,所以采取实现.NET中INullable接口的方式来实现,INullable接口是一个包括了IsNull属性的接口,其定义为:

public interface INullable
{
    bool IsNull { get; }
}

上图举例了简单的几个方法和属性,其中User的定义为:

public class User : IUser
{
    public void Login()
    {
        Console.Write("User Login Now.");
    }
    public void GetInfo()
    {
        Console.Write("User Logout Now.");
    }
    public bool IsNull
    {
        get { return false; }
    }
}
public class NullUser: IUser
{
    public void Login()
    {
        //不做任何处理
    }
    public void GetInfo(){ }
    public bool IsNull
    {
        get { return true; }
    }
}

//同时通过UserManager类来完成对User的操作和管理,基于对null object 的引入,实现的方式可以为:
class UserManager
{
    private IUser user  = new User();
    public IUser User
    {
        get { return user; }
        set 
        {
            user = value ?? new NullUser():
        }
    }
}
//下面是有效的测试
public static void Main()
{
    UserNanager manager = new UserManager();
    //强制为null
    manager.User = null;
    //执行正常
    manager.User.Login();
    if(manager.User.IsNull)
    {
        Console.Write("用户不存在,请检查。");
    }
}

null object模式小结:

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

摘自《你必须知道的.NET》第二十一回 认识全面的null

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值