从NullObject谈C#6.0改进

什么是空引用异常

作为一个敲过代码的码农来说,似乎没有谁没有遇到过NullReferenceException

个问题,有些时候当方法内部调用一个属性、方法(委托)时,我们控制这些属性在“外部”的表现(当然某些情况下使用ref关键字除外),所以我们要在方法
的内部去判断属性、委托方法是否为Null来避免可能的、错误使用上带来的空引用异常,这样当我们知道如果对象为Null的话,我们会实现符合我们“预
期”的行为。

解决空引用异常---Check Any Where

这很简单,我只要在需要用的地方检查一下是否为Null就可以了。是的,这非常简单,语义也很清晰,但是当你要重复检查一个对象实体10000万次时,你的代码中将存在10000个如下代码段:


  1. public void Check() 
  2.         { 
  3.             if (Person.AlivePerson() != null) 
  4.             { 
  5.                 Person.AlivePerson().KeepAlive = true; 
  6.             }  
  7.         } 

你能容忍这样的行为吗?

If(OK)

Continue;

Else

Close;

应用NullObject设计模式

NullObjectPattern出自forth by Gamma(设计模式4人组),核心内容是:提供一个给定对象的空值代理,空值代理中提供不做任何事情的方法实现。

接下来让我们看看维基百科上的C#实现:


  1. // compile as Console Application, requires C# 3.0 or higher 
  2. using System; 
  3. using System.Linq; 
  4. namespace MyExtensionWithExample { 
  5.     public static class StringExtensions {  
  6.         public static int SafeGetLength(this string valueOrNull) {  
  7.             return (valueOrNull ?? string.Empty).Length;  
  8.         } 
  9.     } 
  10.     public static class Program { 
  11.         // define some strings 
  12.         static readonly string[] strings = new [] { "Mr X.", "Katrien Duck", null, "Q" }; 
  13.         // write the total length of all the strings in the array 
  14.         public static void Main(string[] args) { 
  15.             var query = from text in strings select text.SafeGetLength(); // no need to do any checks here 
  16.             Console.WriteLine(query.Sum()); 
  17.         // The output will be: 18  
  18.         } 
  19.     } 

在C#语言中,我们通过静态的扩展方法来实现将检查方式统一在方法内部,而不是写的到处都是,上面的例子中是在String类上实现了一个SafeGetLength扩展方法,将为所有String类型提供了一个方法,这样我们在“代码整洁”上又进了一步。

下面我们再来看一个更常用的例子---来自于StackOverFlow


  1. public static class EventExtensions 
  2.     { 
  3.         public static void Raise<T>(this EventHandler<T> evnt, object sender, T args) 
  4.             where T : EventArgs 
  5.         { 
  6.             if (evnt != null) 
  7.             { 
  8.                 evnt(sender, args); 
  9.             } 
  10.         } 
  11.     } 

最后,说一个细节问题,以上代码均没有实现“线程安全”,在大牛Eric Lippert的文章中针对线程安全有过一个更精彩的讨论,请戳这里

改进后的代码时在方法内部增加了一个临时变量,作为方法内部的拷贝,实现线程安全,如果有疑问请参考我的《C#堆vs栈》中对方法内部变量在堆栈上的表现一章。


  1. public class SomeClass 
  2.     { 
  3.         public event EventHandler<EventArgs> MyEvent; 
  4.  
  5.         private void DoSomething() 
  6.         { 
  7.             var tmp = MyEvent; 
  8.  
  9.             tmp.Raise(this, EventArgs.Empty); 
  10.         } 
  11.     } 

 

更“潮”的方式-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 检查。请看看以下示例:


  1. public static string Truncate(string value, int length) 
  2.   string result = value; 
  3.   if (value != null) // Skip empty string check for elucidation 
  4.   { 
  5.     result = value.Substring(0, Math.Min(value.Length, length)); 
  6.   } 
  7.   return result; 

如果不进行 null 检查,此方法会引发 NullReferenceException。尽管这很简单,但检查字符串参数是否为 null
的过程却稍微有些繁琐。通常,考虑到比较的频率,该繁琐的方法可能没有必要。C# 6.0 包括一个新的 null
条件运算符,可帮助您更加简便地编写这些检查:


  1. public static string Truncate(string value, int length) 
  2. {           
  3.   return value?.Substring(0, Math.Min(value.Length, length)); 
  4.  
  5. [TestMethod] 
  6. public void Truncate_WithNull_ReturnsNull() 
  7.   Assert.AreEqual<string>(null, Truncate(null, 42)); 

根据 Truncate_WithNull_ReturnsNull 方法所演示的内容,如果对象的值实际上为 null,则 null 条件运算符将返回 null。这带来了一个问题,即 null 条件运算符在调用链中出现时会是什么情况?如以下示例中所示:


  1. public static string AdjustWidth(string value, int length) 
  2.   return value?.Substring(0, Math.Min(value.Length, length)).PadRight(length); 
  3.  
  4. [TestMethod] 
  5. public void AdjustWidth_GivenInigoMontoya42_ReturnsInigoMontoyaExtended() 
  6.   Assert.AreEqual<int>(42, AdjustWidth("Inigo Montoya", 42).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 条件运算符执行此操作:


  1. public static IEnumerable<T> GetValueTypeItems<T>( 
  2.   IList<T> collection, params int[] indexes) 
  3.   where T : struct 
  4.   foreach (int index in indexes) 
  5.   { 
  6.     T? item = collection?[index]; 
  7.     if (item != null) yield return (T)item; 
  8.   } 

此示例使用了运算符 ?[…] 的 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


  1. class Theremostat 
  2.   event EventHandler<float> OnTemperatureChanged; 
  3.   private int _Temperature; 
  4.   public int Temperature 
  5.   { 
  6.     get 
  7.     { 
  8.       return _Temperature; 
  9.     } 
  10.     set 
  11.     { 
  12.       // If there are any subscribers, then 
  13.       // notify them of changes in temperature 
  14.       EventHandler<float> localOnChanged = 
  15.         OnTemperatureChanged; 
  16.       if (localOnChanged != null) 
  17.       { 
  18.         _Temperature = value; 
  19.         // Call subscribers 
  20.         localOnChanged(this, value); 
  21.       } 
  22.     } 
  23.   } 

通过使用 null 条件运算符,整个 set 实现过程就可简化为:

OnTemperatureChanged?.Invoke(this, value)

现在,您只需对将 null 条件运算符作为前缀的 Invoke 进行调用,不再需要将委托实例分配给本地变量,从而实现线程安全,甚至是在调用委托之前显式检查值是否为 null。

C# 开发人员都很想知道在最新的四个版本中是否对此内容有所改进。答案是最终进行了改进。仅此一项功能就可以改变调用委托的方式。

另一个 null 条件运算符普及的常见模式是与 coalesce 运算符结合使用。您无需在调用 Length 之前对 linesOfCode 进行 null 检查,而是可以编写项目计数算法,如下所示:

List<string> linesOfCode = ParseSourceCodeFile("Program.cs"); return linesOfCode?.Count ?? 0;

在这种情况下,任何空集合(无项目)和 null 集合均标准化为返回相同数量。总之,null 条件运算符将实现以下功能:

1.  如果操作数为 null,则返回 null

2.  如果操作数为 null,则简化调用链中的其他调用

3.  如果目标成员返回一个值类型,则返回可以为 null 的类型 (System.Nullable<T>)。

4.  以线程安全的方式支持委托调用

5.  可用作成员运算符 (?.) 和索引运算符 (?[…])


作者:史蒂芬King

来源:51CTO

时间: 2024-12-20 14:39:57

从NullObject谈C#6.0改进的相关文章

用 ASP.NET 2.0 改进的 ViewState 加快网站速度

asp.net|速度     如果您是个经验丰富的 ASP.NET 开发人员,一提起 ViewState ,您可能会不寒而栗,因为您想到的是大量通过"鸡尾酒吸管"吸入的 Base64 编码数据.除非采取步骤进行预防,否则大部分 ASP.NET 页面将有大量辅助数据被存储在一个名为 __VIEWSTATE 的隐藏字段中,多数情况下,甚至不需要这个字段.浏览用 ASP.NET 生成的您喜爱的站点,查看页面源代码,计算隐藏在 __VIEWSTATE 字段中的字符数.我尝试了一下,数量为 80

浅谈Sticky组件的改进实现_javascript技巧

在上一篇文章使用getBoundingClientRect方法实现简洁的sticky组件的方法介绍了一个sticky组件的简洁实现,经过这两天的思考,发现上次提供的实现还有较多不足的地方,另外跟别的网站上实现的效果在取消固定的时候也有一些不同,上次提供的取消固定的处理方式不好,本文在上文的基础上,提供一个改进版的sticky组件,功能更加完善,希望您有兴趣阅读. 1. 旧版本的问题 上一个sticky组件的实现中,有多个问题存在: 第一,从sticky的效果上来说,sticky元素在固定前后,不

.NET 4.0改进的介绍

大部分的新特性都是围绕自定义和扩展来的,原先不能自定义的东西现在可以自定义了,可以自己扩展了,然后把自己的组件在web.config中配置即可应用我们自己的组件. Web.Config精简 .NET框架4.0中会把大部分配置放到machine.config中,这样在web.config中甚至可以一行代码都不写. 自定义输出缓存策略 输出缓存对于改善性能有很大好处,在ASP.NET 4.0中可以自定义输出缓存的策略,比如把输出保存在磁盘中,外部的memcached服务中等等.甚至还可以定义一些高级

浅谈:Web2.0在个人网站中的价值

前一段时间写了一篇<一个合格的网站策划应该做到的>.好多网站站长向我咨询,由于时间和精力有限,所以没能一一回复,还请各位朋友鉴谅.我想就我对目前许多个人网站的现状和发展来谈谈个人网站发展的问题,以及web2.0理念对个人网站的影响. 目前大多数个人站长走的都是网站流量线路,把流量和网站排名看的比较重要,当然不乏其中还有一批以web2.0理念为发展的新网站,但是这种网站的发展还占少数,多数站长对什么是web2.0都不是很理解,更不用说彻底理解web2.0的理念. 一.目前个人网站的一般有以下几种

也谈论坛“BBS2.0”的十大升级方向

近日,国内CHINABBS.DONEWS. 纵横论坛以及中国TNT论坛等各大论坛频频发力,开始了新一轮论坛竞争大战,这一方面预示着论坛的价值正在被2.0朝中的创业者们重新认识,一方面也预示了在新一轮论坛大战中,升级性的创新呼之欲出.那么,目前的论坛BBS出从哪些方面进行创新?     论坛的凝聚内容和关系的真正的大集市,但是传统的论坛在关系凝聚和内容凝聚方面存在一定的缺陷,在内容提升和交流把关方面存在一定的缺陷,在跨版互动和全方向交流方面存在一定的缺陷,在个人化与内容之间的平衡感方面也存在一定的

浅谈关于Seo3.0的五大问题

很多站长还没有听说过Seo3.0的概念,笔者就"SEO3.0"提出五大问题并做一个简要分析. 一.什么是Seo3.0? SEO3.0是随着随着搜索引擎的发展逐步出现的概念,如今搜索引擎越来越重视用户体验,网站的用户体验逐渐成为网站排名的决定性因素.今年,百度连续数月的持续更新,不断强调着优质的用户体验的重要性,回归用户这一根本,不仅仅成为搜索引擎的价值核心,也将成为站长运营网站的核心工作.为用户创造独特而有价值的内容,首先,我们要清楚的明白网站存在的根本点在哪里,在于用户,没有满足用户

站长在线:浅谈PR从0直接升到3的成功经验

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 有一段时间没有在A5上面发布文章了,最近公司的事情比较多,也比较忙,所以对网站的关注就少了许多,今天难得抽出来点儿时间,就和大家一起分享一下站长在线PR从0直接升到3的成功经验. 在此之前,我非常感谢A5对我的大力支持,对于我在A5发表的文章全部都在首页展示,这点我感到非常欣慰.因此我个人也非常喜欢在A5上面分享自己的经验,因为他给了我动力,

Cassandra1.0改进:数据压缩

Cassandra 1.0提供了基于ColumnFamily的数据压缩,这也是一个人民群众呼声很高的功能.压缩功能能够有效地减少数据体积,同时也能减少磁盘I/O,特别是对那些读多的应用场景. 压缩有哪些好处 压缩可以有效的减小数据体积,可以在相同的内存和磁盘上存储更多的数据.除此之外,通过只解压指定部分的数据块,Cassandra对从磁盘上读取数据的性能也有提升. 与传统数据库系统不同,传统的数据库系统使用压缩通常会对写性能有负面影响,因为传统方法需要先解压原始数据,个性原始数据,再将个性后的数

谈下web2.0网站的发展!

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断淘宝客 站长团购 云主机 技术大厅 知道web2.0的网站模式是去年的时候,最典型的是猫扑,博客网等网站,就是以用户为中心的互动性的网站. 早就听说猫扑要上市,博客网也要上市,炒的还挺火,后来就偃旗息鼓了.究其原因我有一些个人看法,供大家参考: 1. 盈利模式不明确,网站现在比较明显的盈利模式有两种那就是:网络广告和会员制. 我从这两个最火的网站上没有看出他们的网络广告