本文来聊一聊们经常会做的空值检查问题,从一个简单的空值检查Any Where,到设计模式的NullObjectPattern,再到C#6.0“可能”会提供的语法,让们体验一次语言开发上的“持续改进”,Let’s go~
AD:
什么是空引用异常
作为一个敲过代码的码农来说,似乎没有谁没有遇到过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); } }