未将对象引用到实例,即NullReferenceException异常,我相信这是c#编程中最常见的错误之一,至少我在做项目的过程中,有很多时候都会抛出这个异常。每当这个异常出现的时候,我都会头皮一紧,因为以我的经验总结,一般情况下不会出现这个错误,但是一旦出现这个错误往往是比较难排查的,特别是代码量较大的时候,而等找到bug时,往往又会出现纠结状态,因为NullReferenceException异常经常和程序的逻辑紧密相连,这就意味着不是你的程序写错了,而很有可能是你的编程逻辑设计的不够合理。
当然,NullReferenceException异常的解决方案也非常的简单,只要在异常处加上类似下面的代码就可以了
1 if(obj != null) 2 { 3 //some actions 4 }
意思就是在使用这个引用之前检查引用是否为空。但事实并非这么简单,正如上面说的,NullReferenceException异常往往和代码设计的逻辑是相关的,特别是代码量大的时候,一旦发现这个错误,往往会导致这一段代码的重构!
而且更重要的是,有时候在一段代码里,需要根据不同的情况来决定一个引用是否为null,或者是确实指向一个真实的对象。这种情况是最纠结的,因为这就意味着每个需要使用到这个引用的地方都要加上上述的代码!
作为一个程序员来说,这是不能容忍的!
因为一个简单的 if(obj != null) 的代码居然重复出现无数次,一篇代码里出现重复模式的代码,我认识这可以看做是程序员的失败!如何消除 if(obj != null) 这个模式呢?一些大牛曾经讲过:
任何时候都不应该使用null,你应该专门设计一个空类来代替null。
至于null指针的“罪与罚”,我想每个程序员都有自己的见解吧,在我看来,null指针可以列入编程史上最大的创新,同时也可列入最大的罪恶之源!
什么叫使用空类来代替null,这里就以日志Logger类作为示例来说明一下(当然,实际项目中你可以使用 log4net,或者其他IOC框架来实现日志记录)
1 public class Logger 2 { 3 private class EmptyLog : Logger 4 { 5 public override void Log(string msg){ } 6 public override void LogErr(string errMsg) { } 7 public override void LogErr(Exception ex) { } 8 } 9 static Logger() 10 { 11 Empty = new EmptyLog(); 12 } 13 public static Logger Empty { get; private set; } 14 15 readonly TextWriter writer; 16 private Logger() { } 17 public Logger(TextWriter writer) 18 { 19 this.writer = writer; 20 } 21 public virtual void Log(string msg) 22 { 23 writer.WriteLine(DateTime.Now); 24 writer.WriteLine(msg); 25 writer.WriteLine(); 26 } 27 public virtual void LogErr(string errMsg) 28 { 29 writer.WriteLine("error"); 30 Log(errMsg); 31 } 32 public virtual void LogErr(Exception ex) 33 { 34 writer.WriteLine(string.Format("error<{0}>",ex.GetType().FullName)); 35 Log(ex.Message); 36 } 37 }
下面是测试代码及测试结果:
1 [STAThread] 2 static void Main() 3 { 4 var log = Logger.Empty; 5 log.Log("hellow world"); 6 log.LogErr("error"); 7 log.LogErr(new NullReferenceException()); 8 log = new Logger(Console.Out); 9 log.Log("hellow world"); 10 log.LogErr("this is a error message"); 11 log.LogErr(new NullReferenceException()); 12 }
可见,任何时候都不应该使用null,你应该专门设计一个空类来代替null,这句话的意思就是不要给一个引用赋值null,而是在初始化时要引用指向一个空类示例!
这种方法确实不错,至少是避免了 if(obj != null) 这种单调乏味的模式,但是我觉得还是有点不爽,因为那个空类纯粹就是一个资源浪费,既然不需要记录日志的地方,那就应该让引用指向null才对,表示什么都没有,而这种方法意味着即使你什么都不做,但是也要消耗一部分资源来供给这个空类使用!这种不必要的资源浪费和重复模式一样,都是我不能容忍的。于是我设计了一个方法:
利用扩展方法来避免 if(obj != null) 模式!
扩展方法和Linq是我最喜欢的两个C#的语言特性,为什么Common Lisp那么强大,就是因为程序员可以自行对Lisp语法进行扩充,定制自己想要的语法,同样C#重点的扩展方法也可以实现扩展语法的功能,下面就是我用扩展方法重新设计的Logger类:
1 public class Logger 2 { 3 readonly TextWriter writer; 4 public Logger(TextWriter writer) 5 { 6 this.writer = writer; 7 } 8 internal void Log(string msg) 9 { 10 writer.WriteLine(DateTime.Now); 11 writer.WriteLine(msg); 12 writer.WriteLine(); 13 } 14 internal void LogErr(string errMsg) 15 { 16 writer.WriteLine("error"); 17 Log(errMsg); 18 } 19 internal void LogErr(Exception ex) 20 { 21 writer.WriteLine(string.Format("error<{0}>",ex.GetType().FullName)); 22 Log(ex.Message); 23 } 24 } 25 public static class LoggerHelper 26 { 27 public static void Log(this Logger logger, string msg) 28 { 29 if (logger != null) 30 logger.Log(msg); 31 } 32 public static void LogErr(this Logger logger, string errMsg) 33 { 34 if (logger != null) 35 logger.LogErr(errMsg); 36 } 37 public static void LogErr(this Logger logger, Exception ex) 38 { 39 if (logger != null) 40 logger.LogErr(ex); 41 } 42 }
下面是测试代码及测试结果:
1 [STAThread] 2 static void Main() 3 { 4 Logger log = null; 5 log.Log("hellow world"); 6 log.LogErr("error"); 7 log.LogErr(new NullReferenceException()); 8 log = new Logger(Console.Out); 9 log.Log("hellow world"); 10 log.LogErr("this is a error message"); 11 log.LogErr(new NullReferenceException()); 12 }
从测试代码可以开出,即使变量log为null时,代码依然可以正常运行,因为我把 if(obj != null) 模式 利用扩展方法进行了封装,这样就可以放心的使用log了,不用担心log是否为null吗,而且在不需要记录日志的时候,直接将log赋值为null即可,不浪费任何资源。
这就是我关于解决“未将对象引用到实例”的方法,如果大家有其它好的办法,欢迎在这里进行交流!