从NullObject谈C#6.0改进
前言
本文来聊一聊我们经常会做的空值检查问题,从一个简单的空值检查Any Where,到设计模式的NullObjectPattern,再到C#6.0“可能”会提供的语法,让我们体验一次语言开发上的“持续改进”,Let’s go~
什么是空引用异常
作为一个敲过代码的码农来说,似乎没有谁没有遇到过NullReferenceException这个问题,有些时候当方法内部调用一个属性、方法(委托)时,我们控制这些属性在“外部”的表现(当然某些情况下使用ref关键字除外),所以我们要在方法的内部去判断属性、委托方法是否为Null来避免可能的、错误使用上带来的空引用异常,这样当我们知道如果对象为Null的话,我们会实现符合我们“预期”的行为。
解决空引用异常---Check Any Where
这很简单,我只要在需要用的地方检查一下是否为Null就可以了。是的,这非常简单,语义也很清晰,但是当你要重复检查一个对象实体10000万次时,你的代码中将存在10000个如下代码段:
- public void Check()
- {
- if (Person.AlivePerson() != null)
- {
- Person.AlivePerson().KeepAlive = true;
- }
- }
你能容忍这样的行为吗?
If(OK)
Continue;
Else
Close;
应用NullObject设计模式
NullObjectPattern出自forth by Gamma(设计模式4人组),核心内容是:提供一个给定对象的空值代理,空值代理中提供不做任何事情的方法实现。
接下来让我们看看维基百科上的C#实现:
- // compile as Console Application, requires C# 3.0 or higher
- using System;
- using System.Linq;
- namespace MyExtensionWithExample {
- public static class StringExtensions {
- public static int SafeGetLength(this string valueOrNull) {
- return (valueOrNull ?? string.Empty).Length;
- }
- }
- public static class Program {
- // define some strings
- static readonly string[] strings = new [] { "Mr X.", "Katrien Duck", null, "Q" };
- // write the total length of all the strings in the array
- public static void Main(string[] args) {
- var query = from text in strings select text.SafeGetLength(); // no need to do any checks here
- Console.WriteLine(query.Sum());
- // The output will be: 18
- }
- }
- }
在C#语言中,我们通过静态的扩展方法来实现将检查方式统一在方法内部,而不是写的到处都是,上面的例子中是在String类上实现了一个SafeGetLength扩展方法,将为所有String类型提供了一个方法,这样我们在“代码整洁”上又进了一步。
下面我们再来看一个更常用的例子---来自于StackOverFlow
- public static class EventExtensions
- {
- public static void Raise<T>(this EventHandler<T> evnt, object sender, T args)
- where T : EventArgs
- {
- if (evnt != null)
- {
- evnt(sender, args);
- }
- }
- }
最后,说一个细节问题,以上代码均没有实现“线程安全”,在大牛Eric Lippert的文章中针对线程安全有过一个更精彩的讨论,请戳这里。
改进后的代码时在方法内部增加了一个临时变量,作为方法内部的拷贝,实现线程安全,如果有疑问请参考我的《C#堆vs栈》中对方法内部变量在堆栈上的表现一章。
- public class SomeClass
- {
- public event EventHandler<EventArgs> MyEvent;
- private void DoSomething()
- {
- var tmp = MyEvent;
- tmp.Raise(this, EventArgs.Empty);
- }
- }
更“潮”的方式-C#6.0语法
来自MSDN Magazine的Mark Michaelis(《C#本质论》作者)给我们介绍了C#6.0在语言可能带来的新改进,其中就有针对“Null条件运算符”的改进。
C#6.0更多参考:
Part One: https://msdn.microsoft.com/zh-cn/magazine/dn683793.aspx
Part Two: https://msdn.microsoft.com/zh-cn/magazine/dn802602.aspx
即使是 .NET 开发新手,也可能非常熟悉 NullReferenceException。有一个例外是几乎总是会指出一个 Bug,因为开发人员在调用 (null) 对象的成员之前未进行充分的 null 检查。请看看以下示例:
- public static string Truncate(string value, int length)
- {
- string result = value;
- if (value != null) // Skip empty string check for elucidation
- {
- result = value.Substring(, Math.Min(value.Length, length));
- }
- return result;
- }
如果不进行 null 检查,此方法会引发 NullReferenceException。尽管这很简单,但检查字符串参数是否为 null 的过程却稍微有些繁琐。通常,考虑到比较的频率,该繁琐的方法可能没有必要。C# 6.0 包括一个新的 null 条件运算符,可帮助您更加简便地编写这些检查:
- public static string Truncate(string value, int length)
- {
- return value?.Substring(, Math.Min(value.Length, length));
- }
- [TestMethod]
- public void Truncate_WithNull_ReturnsNull()
- {
- Assert.AreEqual<string>(null, Truncate(null, ));
- }
根据 Truncate_WithNull_ReturnsNull 方法所演示的内容,如果对象的值实际上为 null,则 null 条件运算符将返回 null。这带来了一个问题,即 null 条件运算符在调用链中出现时会是什么情况?如以下示例中所示:
- public static string AdjustWidth(string value, int length)
- {
- return value?.Substring(, Math.Min(value.Length, length)).PadRight(length);
- }
- [TestMethod]
- public void AdjustWidth_GivenInigoMontoya42_ReturnsInigoMontoyaExtended()
- {
- Assert.AreEqual<int>(, AdjustWidth("Inigo Montoya", ).Length);
- }
尽管 Substring 是通过 null 条件运算符进行调用的,并且 null value?.Substring 似乎返回了 null,但语言行为按您的想法进行。这简化了对 PadRight 的调用过程,并立即返回 null,从而避免会导致出现 NullReferenceException 的编程错误。这个概念称为“null 传播”。
Null 条件运算符会根据具体条件进行 null 检查,然后再调用目标方法以及调用链中的所有其他方法。这将可能产生一个令人惊讶的结果,例如,text?.Length.GetType 语句中的结果。
如果 null 条件运算符在调用目标为 null 时返回 null,那么调用会返回值类型的成员时最终会是什么数据类型(假定值类型不能为 null)?例如,从 value?.Length 返回的数据类型不能只是 int。答案当然是:可以为 null 的类型(int?)。实际上,尝试仅将结果分配给 int 将会出现编译错误:
- int length = text?.Length; // Compile Error: Cannot implicitly convert type 'int?' to 'int'
Null 条件具有两种语法形式。首先,问号在点运算符前面 (?.)。其次,将问号和索引运算符结合使用。例如,给定一个集合(而非在索引到集合之前显式进行 null 检查),您就可以使用 null 条件运算符执行此操作:
- public static IEnumerable<T> GetValueTypeItems<T>(
- IList<T> collection, params int[] indexes)
- where T : struct
- {
- foreach (int index in indexes)
- {
- T? item = collection?[index];
- if (item != null) yield return (T)item;
- }
- }
此示例使用了运算符 ?[…] 的 null 条件索引形式,导致仅在集合不为 null 时才索引到集合。通过 null 条件运算符的此形式,T? item = collection?[index] 语句在行为上相当于:
T? item = (collection != null) ? collection[index] : null.
请注意,null 条件运算符仅可检索项目,不会分配项目。如果给定 null 集合,那么这意味着什么?
请注意针对引用类型使用 ?[…] 时的隐式歧义。由于引用类型可以为 null,因此对于集合是否为 null,或者是否元素本身实际上就是 null 而言,来自 ?[…] 运算符的 null 结果不明确。
Null 条件运算符的一个非常有用的应用程序解决了 C# 自 C# 1.0 以来一直存在的的一个特性,即在调用委托之前检查是否为 null。我们来看一下图中显示的 C# 2.0 代码。
图 1 在调用委托之前检查是否为 Null
- class Theremostat
- {
- event EventHandler<float> OnTemperatureChanged;
- private int _Temperature;
- public int Temperature
- {
- get
- {
- return _Temperature;
- }
- set
- {
- // If there are any subscribers, then
- // notify them of changes in temperature
- EventHandler<float> localOnChanged =
- OnTemperatureChanged;
- if (localOnChanged != null)
- {
- _Temperature = value;
- // Call subscribers
- localOnChanged(this, value);
- }
- }
- }
- }
通过使用 null 条件运算符,整个 set 实现过程就可简化为:
- OnTemperatureChanged?.Invoke(this, value)
现在,您只需对将 null 条件运算符作为前缀的 Invoke 进行调用,不再需要将委托实例分配给本地变量,从而实现线程安全,甚至是在调用委托之前显式检查值是否为 null。
C# 开发人员都很想知道在最新的四个版本中是否对此内容有所改进。答案是最终进行了改进。仅此一项功能就可以改变调用委托的方式。
另一个 null 条件运算符普及的常见模式是与 coalesce 运算符结合使用。您无需在调用 Length 之前对 linesOfCode 进行 null 检查,而是可以编写项目计数算法,如下所示:
- List<string> linesOfCode = ParseSourceCodeFile("Program.cs");
- return linesOfCode?.Count ?? ;
在这种情况下,任何空集合(无项目)和 null 集合均标准化为返回相同数量。总之,null 条件运算符将实现以下功能:
1. 如果操作数为 null,则返回 null
2. 如果操作数为 null,则简化调用链中的其他调用
3. 如果目标成员返回一个值类型,则返回可以为 null 的类型 (System.Nullable<T>)。
4. 以线程安全的方式支持委托调用
5. 可用作成员运算符 (?.) 和索引运算符 (?[…])
引用
http://stackoverflow.com/questions/13629051/net-event-raising-and-nullobject-pattern ---线程安全的扩展机制
https://msdn.microsoft.com/zh-cn/magazine/dn802602.aspx ---C#6.0 Null条件运算符
http://en.wikipedia.org/wiki/Null_Object_pattern ---维基百科上的NullObjectPattern解释
从NullObject谈C#6.0改进的更多相关文章
- 浅谈Android Studio3.0更新之路(遇坑必入)
>可以参考官网设置-> 1 2 >> Fantasy_Lin_网友评论原文地址是:简书24K纯帅豆写的我也更新一下出处[删除]Fa 转自脚本之家 浅谈Android Studi ...
- 浅谈Android 6.0之Runtime Permissions
前言 Android6.0发布后,其一系列新特新足够让我们这些Android程序员兴奋一段时间了.首先我们先看看具体有哪些新特性: -锁频下语音搜索 -指纹识别 -更完整的应用权限管理 -Doze电量 ...
- 数据库2.0改进e-r图
1.新建教师实体集并将1.0中的任课教师,教务老师归类为教师. 2.将实体集考勤表设置为弱实体集. 3.将学生与考勤表的关系由属于关系设置为出勤关系. 4.出勤关系中设置出勤记录属性和教师留言属性. ...
- 浅谈Vue.js2.0核心思想
Vue.js是一个提供MVVM数据双向绑定的库,专注于UI层面,核心思想是:数据驱动.组件系统. 数据驱动: Vue.js数据观测原理在技术实现上,利用的是ES5Object.defineProper ...
- 浅谈Vue.js2.0某些概念
Vue.js2.0是一套构建用户界面的渐进式框架,目标是实现数据驱动和组件系统. A 渐进式框架 Vue.js是一个提供MVVM数据双向绑定的库,只专注于UI层面,这是它的核心.它本身没有解决SP ...
- 浅谈工业4.0背景下的空中数据端口,无人机3D 可视化系统的应用
前言 近年来,无人机的发展越发迅速,既可民用于航拍,又可军用于侦察,涉及行业广泛,把无人机想象成一个“会飞的传感器”,无人机就成了工业4.0的一个空中数据端口,大至地球物理.气象.农业数据.小至个人位 ...
- [C#]6.0新特性浅谈
原文:[C#]6.0新特性浅谈 C#6.0出来也有很长一段时间了,虽然新的特性和语法趋于稳定,但是对于大多数程序猿来说,想在工作中用上C#6.0估计还得等上不短的一段时间.所以现在再来聊一聊新版本带来 ...
- Jenkins 2.0 要来了
Jenkins 在2016/02/29日发布了2.0 alpha版本,https://jenkins-ci.org/2.0/ , 改进界面,向前兼容,增加新功能: 1.初始化时可以选择推荐插件或自定义 ...
- Android 6.0 新特性
首先谈一谈Android 6.0的一些新特性 锁屏下语音搜索 指纹识别 更完整的应用权限管理 Doze电量管理 Now onTap App link 在开发过程中与我们关系最密切的就是"更完 ...
随机推荐
- 在启动dubbo框架时报错。Unable to connect to zookeeper server within timeout: 5000
这是因为zookeeper服务没有启动,所以会报错超时.只要启动zookeeper就行了. zookerper的启动很简单的,网上随便搜搜都有.
- fastreport 如何 设置 richview 的 行高
richview中的行高改变有点特别.必须在AfterData 事件执行的时候才能修改: 也就是说,如果简单的放一个按钮,去发送消息给richView->RichEdit ,然后调用frxRep ...
- C# 接口应用及意义
写在前面:新手入行,读者勉强看看吧,写的不对的欢迎讨论,板砖轻拍! 一.定义 接口描述的是可属于任何类或结构的一组相关功能,所以实现接口的类或结构必须实现接口定义中指定的接口成员. 通常用Interf ...
- 不使用容器构建Registry
安装必要的软件 $ sudo apt-get install build-essential python-dev libevent-dev python-pip liblzma-dev 配置 doc ...
- bash脚本编程之二 字符串测试及for循环
shell中大量的测试和比较选项而困惑呢? 这个技巧可以帮助您解密不同类型的文件.算术和字符串测试,这样您就能够知道什么时候使用 test. [ ]. [[ ]].(( )) 或 if-then-el ...
- jQuery之ajax的跨域获取数据
如果获取的数据文件存放在远程服务器上(域名不同,也就是跨域获取数据),则需要使用jsonp类型.使用这种类型的话,会创建一个查询字符串参数 callback=? ,这个参数会加在请求的URL后面.服务 ...
- LR录制https协议报证书错误,导航已阻止
使用IE浏览器录制https协议报证书错误,导航已阻止,修改如下配置文件:
- 《Linux内核设计与实现》读书笔记 第二章 从内核出发
一.获取内核源码 1. Git git实际上是一种开源的分布式版本控制工具. Linux作为一个开源的内核,其源代码也可以用git下载和管理 - 获取最新提交到版本树的一个副本 - $ git clo ...
- 解决Ubuntu下 Could NOT find CURL (missing: CURL_LIBRARY CURL_INCLUDE_DIR)
Ubuntu下CMake 编译时出现问题:Could NOT find CURL (missing: CURL_LIBRARY CURL_INCLUDE_DIR) 查找发现 # sudo apt-g ...
- kafka命令
./kafka-topics.sh --zookeeper ip:port --list ./kafka-topics.sh --create --topic test --zookeeper ip: ...