新聞中心
什么是空引用異常

創(chuàng)新互聯(lián)主要從事網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)千陽,十年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18982081108
作為一個(gè)敲過代碼的碼農(nóng)來說,似乎沒有誰沒有遇到過NullReferenceException這 個(gè)問題,有些時(shí)候當(dāng)方法內(nèi)部調(diào)用一個(gè)屬性、方法(委托)時(shí),我們控制這些屬性在“外部”的表現(xiàn)(當(dāng)然某些情況下使用ref關(guān)鍵字除外),所以我們要在方法 的內(nèi)部去判斷屬性、委托方法是否為Null來避免可能的、錯(cuò)誤使用上帶來的空引用異常,這樣當(dāng)我們知道如果對(duì)象為Null的話,我們會(huì)實(shí)現(xiàn)符合我們“預(yù) 期”的行為。
解決空引用異常---Check Any Where
這很簡單,我只要在需要用的地方檢查一下是否為Null就可以了。是的,這非常簡單,語義也很清晰,但是當(dāng)你要重復(fù)檢查一個(gè)對(duì)象實(shí)體10000萬次時(shí),你的代碼中將存在10000個(gè)如下代碼段:
- public void Check()
- {
- if (Person.AlivePerson() != null)
- {
- Person.AlivePerson().KeepAlive = true;
- }
- }
你能容忍這樣的行為嗎?
If(OK)
Continue;
Else
Close;
應(yīng)用NullObject設(shè)計(jì)模式
NullObjectPattern出自forth by Gamma(設(shè)計(jì)模式4人組),核心內(nèi)容是:提供一個(gè)給定對(duì)象的空值代理,空值代理中提供不做任何事情的方法實(shí)現(xiàn)。
接下來讓我們看看維基百科上的C#實(shí)現(xiàn):
- // 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#語言中,我們通過靜態(tài)的擴(kuò)展方法來實(shí)現(xiàn)將檢查方式統(tǒng)一在方法內(nèi)部,而不是寫的到處都是,上面的例子中是在String類上實(shí)現(xiàn)了一個(gè)SafeGetLength擴(kuò)展方法,將為所有String類型提供了一個(gè)方法,這樣我們?cè)凇按a整潔”上又進(jìn)了一步。
下面我們?cè)賮砜匆粋€(gè)更常用的例子---來自于StackOverFlow
- public static class EventExtensions
- {
- public static void Raise
(this EventHandler evnt, object sender, T args) - where T : EventArgs
- {
- if (evnt != null)
- {
- evnt(sender, args);
- }
- }
- }
***,說一個(gè)細(xì)節(jié)問題,以上代碼均沒有實(shí)現(xiàn)“線程安全”,在大牛Eric Lippert的文章中針對(duì)線程安全有過一個(gè)更精彩的討論,請(qǐng)戳這里。
改進(jìn)后的代碼時(shí)在方法內(nèi)部增加了一個(gè)臨時(shí)變量,作為方法內(nèi)部的拷貝,實(shí)現(xiàn)線程安全,如果有疑問請(qǐng)參考我的《C#堆vs?!分袑?duì)方法內(nèi)部變量在堆棧上的表現(xiàn)一章。
- public class SomeClass
- {
- public event EventHandler
MyEvent; - private void DoSomething()
- {
- var tmp = MyEvent;
- tmp.Raise(this, EventArgs.Empty);
- }
- }
#p#
更“潮”的方式-C#6.0語法
來自MSDN Magazine的Mark Michaelis(《C#本質(zhì)論》作者)給我們介紹了C#6.0在語言可能帶來的新改進(jìn),其中就有針對(duì)“Null條件運(yùn)算符”的改進(jìn)。
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 開發(fā)新手,也可能非常熟悉 NullReferenceException。有一個(gè)例外是幾乎總是會(huì)指出一個(gè) Bug,因?yàn)殚_發(fā)人員在調(diào)用 (null) 對(duì)象的成員之前未進(jìn)行充分的 null 檢查。請(qǐng)看看以下示例:
- public static string Truncate(string value, int length)
- {
- string result = value;
- if (value != null) // Skip empty string check for elucidation
- {
- result = value.Substring(0, Math.Min(value.Length, length));
- }
- return result;
- }
如果不進(jìn)行 null 檢查,此方法會(huì)引發(fā) NullReferenceException。盡管這很簡單,但檢查字符串參數(shù)是否為 null 的過程卻稍微有些繁瑣。通常,考慮到比較的頻率,該繁瑣的方法可能沒有必要。C# 6.0 包括一個(gè)新的 null 條件運(yùn)算符,可幫助您更加簡便地編寫這些檢查:
- public static string Truncate(string value, int length)
- {
- return value?.Substring(0, Math.Min(value.Length, length));
- }
- [TestMethod]
- public void Truncate_WithNull_ReturnsNull()
- {
- Assert.AreEqual
(null, Truncate(null, 42)); - }
根據(jù) Truncate_WithNull_ReturnsNull 方法所演示的內(nèi)容,如果對(duì)象的值實(shí)際上為 null,則 null 條件運(yùn)算符將返回 null。這帶來了一個(gè)問題,即 null 條件運(yùn)算符在調(diào)用鏈中出現(xiàn)時(shí)會(huì)是什么情況?如以下示例中所示:
- public static string AdjustWidth(string value, int length)
- {
- return value?.Substring(0, Math.Min(value.Length, length)).PadRight(length);
- }
- [TestMethod]
- public void AdjustWidth_GivenInigoMontoya42_ReturnsInigoMontoyaExtended()
- {
- Assert.AreEqual
(42, AdjustWidth("Inigo Montoya", 42).Length); - }
盡管 Substring 是通過 null 條件運(yùn)算符進(jìn)行調(diào)用的,并且 null value?.Substring 似乎返回了 null,但語言行為按您的想法進(jìn)行。這簡化了對(duì) PadRight 的調(diào)用過程,并立即返回 null,從而避免會(huì)導(dǎo)致出現(xiàn) NullReferenceException 的編程錯(cuò)誤。這個(gè)概念稱為“null 傳播”。
Null 條件運(yùn)算符會(huì)根據(jù)具體條件進(jìn)行 null 檢查,然后再調(diào)用目標(biāo)方法以及調(diào)用鏈中的所有其他方法。這將可能產(chǎn)生一個(gè)令人驚訝的結(jié)果,例如,text?.Length.GetType 語句中的結(jié)果。
如果 null 條件運(yùn)算符在調(diào)用目標(biāo)為 null 時(shí)返回 null,那么調(diào)用會(huì)返回值類型的成員時(shí)最終會(huì)是什么數(shù)據(jù)類型(假定值類型不能為 null)?例如,從 value?.Length 返回的數(shù)據(jù)類型不能只是 int。答案當(dāng)然是:可以為 null 的類型(int?)。實(shí)際上,嘗試僅將結(jié)果分配給 int 將會(huì)出現(xiàn)編譯錯(cuò)誤:
int length = text?.Length; // Compile Error: Cannot implicitly convert type 'int?' to 'int'
Null 條件具有兩種語法形式。首先,問號(hào)在點(diǎn)運(yùn)算符前面 (?.)。其次,將問號(hào)和索引運(yùn)算符結(jié)合使用。例如,給定一個(gè)集合(而非在索引到集合之前顯式進(jìn)行 null 檢查),您就可以使用 null 條件運(yùn)算符執(zhí)行此操作:
- public static IEnumerable
GetValueTypeItems ( - IList
collection, params int[] indexes) - where T : struct
- {
- foreach (int index in indexes)
- {
- T? item = collection?[index];
- if (item != null) yield return (T)item;
- }
- }
此示例使用了運(yùn)算符 ?[…] 的 null 條件索引形式,導(dǎo)致僅在集合不為 null 時(shí)才索引到集合。通過 null 條件運(yùn)算符的此形式,T? item = collection?[index] 語句在行為上相當(dāng)于:
T? item = (collection != null) ? collection[index] : null.
請(qǐng)注意,null 條件運(yùn)算符僅可檢索項(xiàng)目,不會(huì)分配項(xiàng)目。如果給定 null 集合,那么這意味著什么?
請(qǐng)注意針對(duì)引用類型使用 ?[…] 時(shí)的隱式歧義。由于引用類型可以為 null,因此對(duì)于集合是否為 null,或者是否元素本身實(shí)際上就是 null 而言,來自 ?[…] 運(yùn)算符的 null 結(jié)果不明確。
Null 條件運(yùn)算符的一個(gè)非常有用的應(yīng)用程序解決了 C# 自 C# 1.0 以來一直存在的的一個(gè)特性,即在調(diào)用委托之前檢查是否為 null。我們來看一下圖中顯示的 C# 2.0 代碼。
圖 1 在調(diào)用委托之前檢查是否為 Null
- class Theremostat
- {
- event EventHandler
OnTemperatureChanged; - private int _Temperature;
- public int Temperature
- {
- get
- {
- return _Temperature;
- }
- set
- {
- // If there are any subscribers, then
- // notify them of changes in temperature
- EventHandler
localOnChanged = - OnTemperatureChanged;
- if (localOnChanged != null)
- {
- _Temperature = value;
- // Call subscribers
- localOnChanged(this, value);
- }
- }
- }
- }
通過使用 null 條件運(yùn)算符,整個(gè) set 實(shí)現(xiàn)過程就可簡化為:
OnTemperatureChanged?.Invoke(this, value)
現(xiàn)在,您只需對(duì)將 null 條件運(yùn)算符作為前綴的 Invoke 進(jìn)行調(diào)用,不再需要將委托實(shí)例分配給本地變量,從而實(shí)現(xiàn)線程安全,甚至是在調(diào)用委托之前顯式檢查值是否為 null。
C# 開發(fā)人員都很想知道在***的四個(gè)版本中是否對(duì)此內(nèi)容有所改進(jìn)。答案是最終進(jìn)行了改進(jìn)。僅此一項(xiàng)功能就可以改變調(diào)用委托的方式。
另一個(gè) null 條件運(yùn)算符普及的常見模式是與 coalesce 運(yùn)算符結(jié)合使用。您無需在調(diào)用 Length 之前對(duì) linesOfCode 進(jìn)行 null 檢查,而是可以編寫項(xiàng)目計(jì)數(shù)算法,如下所示:
List
在這種情況下,任何空集合(無項(xiàng)目)和 null 集合均標(biāo)準(zhǔn)化為返回相同數(shù)量。總之,null 條件運(yùn)算符將實(shí)現(xiàn)以下功能:
1. 如果操作數(shù)為 null,則返回 null
2. 如果操作數(shù)為 null,則簡化調(diào)用鏈中的其他調(diào)用
3. 如果目標(biāo)成員返回一個(gè)值類型,則返回可以為 null 的類型 (System.Nullable
4. 以線程安全的方式支持委托調(diào)用
5. 可用作成員運(yùn)算符 (?.) 和索引運(yùn)算符 (?[…])
示例代碼下載
引用
http://stackoverflow.com/questions/13629051/net-event-raising-and-nullobject-pattern ---線程安全的擴(kuò)展機(jī)制
https://msdn.microsoft.com/zh-cn/magazine/dn802602.aspx ---C#6.0 Null條件運(yùn)算符
http://en.wikipedia.org/wiki/Null_Object_pattern ---維基百科上的NullObjectPattern解釋
作者:Stephen Cui
出處:http://www.cnblogs.com/cuiyansong
網(wǎng)頁名稱:從NullObject談C#6.0改進(jìn)
文章出自:http://m.fisionsoft.com.cn/article/dpheoog.html


咨詢
建站咨詢
