从NullObject谈C#6.0改进

什么是空引用异常

创新互联主要从事网站设计、网站建设、网页设计、企业做网站、公司建网站等业务。立足成都服务千阳,十年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:18982081108

作为一个敲过代码的码农来说,似乎没有谁没有遇到过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.     }
  20. }

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

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

  
 
  1. public static class EventExtensions
  2.     {
  3.         public static void Raise(this EventHandler 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 MyEvent;
  4.         private void DoSomething()
  5.         {
  6.             var tmp = MyEvent;
  7.             tmp.Raise(this, EventArgs.Empty);
  8.         }
  9.     }

#p#

更“潮”的方式-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. {
  3.   string result = value;
  4.   if (value != null) // Skip empty string check for elucidation
  5.   {
  6.     result = value.Substring(0, Math.Min(value.Length, length));
  7.   }
  8.   return result;
  9. }

如果不进行 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. {
  8.   Assert.AreEqual(null, Truncate(null, 42));
  9. }

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

  
 
  1. public static string AdjustWidth(string value, int length)
  2. {
  3.   return value?.Substring(0, Math.Min(value.Length, length)).PadRight(length);
  4. }
  5. [TestMethod]
  6. public void AdjustWidth_GivenInigoMontoya42_ReturnsInigoMontoyaExtended()
  7. {
  8.   Assert.AreEqual(42, AdjustWidth("Inigo Montoya", 42).Length);
  9. }

尽管 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 GetValueTypeItems(
  2.   IList collection, params int[] indexes)
  3.   where T : struct
  4. {
  5.   foreach (int index in indexes)
  6.   {
  7.     T? item = collection?[index];
  8.     if (item != null) yield return (T)item;
  9.   }
  10. }

  此示例使用了运算符 ?[…] 的 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. {
  3.   event EventHandler OnTemperatureChanged;
  4.   private int _Temperature;
  5.   public int Temperature
  6.   {
  7.     get
  8.     {
  9.       return _Temperature;
  10.     }
  11.     set
  12.     {
  13.       // If there are any subscribers, then
  14.       // notify them of changes in temperature
  15.       EventHandler localOnChanged =
  16.         OnTemperatureChanged;
  17.       if (localOnChanged != null)
  18.       {
  19.         _Temperature = value;
  20.         // Call subscribers
  21.         localOnChanged(this, value);
  22.       }
  23.     }
  24.   }
  25. }

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

OnTemperatureChanged?.Invoke(this, value)

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

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

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

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

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

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

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

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

  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解释

作者:Stephen Cui
出处:http://www.cnblogs.com/cuiyansong

标题名称:从NullObject谈C#6.0改进
分享链接:http://www.shufengxianlan.com/qtweb/news25/77025.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联