.Net令人糾結的Null

作者: 小城故事  來源: 博客園  發布時間: 2010-12-22 22:13  閱讀: 2400 次  推薦: 0   原文鏈接   [收藏]  
摘要:從我們剛學.Net編程起,我們的程序不斷被從天而降NullReferenceException打斷。直到今天,我們仍然時常為C#的Null或者VB的Nothing困惑。該文將給你解除疑惑。

  從我們剛學.Net編程起,我們的程序不斷被從天而降NullReferenceException打斷。直到今天,我們仍然時常為C#的Null或者VB的Nothing困惑。什么情況下我們該返回null,如果參數是null代表什么。許多類型,有兩種不同意義的空狀態,一種是null,一種是其本身或其某個屬性集合中沒有元素,這就更容易產生誤用。常聽有人說,Null這個概念在編程語言中根本不應該存在。但是,從C++到Java到.Net,它從未離開過。

  最近,注意到.Net Framework在讀取程序配置文件的一個小Bug。比如我在配置文件中,自定義了名為ReviewPeriod的節點:

 
<configuration>
<configSections>
<section name="reviewPeriod" type="WordPadTest.ReviewPeriod,WordPadTest"/>
</configSections>
<reviewPeriod>
<Periods>
<period id="1" />
<period id="2" name="d" />
<period id="3" name="m" />
</Periods>
</reviewPeriod>
</configuration>

  注意第一個period節點沒有定義name屬性。.Net Framework中關于程序配置處理的類都在System.Configuration命名空間下,應用很廣泛,比如Asp.Net和WCF項目,還有許多第三方框架如NHibernate。下面定義三個類,分別描述自定義節點配置,自定義節點集合,自定義節點實體,這也是最普通的程序自定義配置處理方式。然后就可以遍歷ConfigurationManager.GetSection("reviewPeriod") as ReviewPeriod 訪問每個period對象。

 
namespace WordPadTest
{

class ReviewPeriod : ConfigurationSection
{
[ConfigurationProperty(
"Periods")]
public Periods Periods
{

get { return this["Periods"] as Periods; }
}
}


class Periods: ConfigurationElementCollection
{

protected override ConfigurationElement CreateNewElement()
{

return new Period();
}


protected override object GetElementKey(ConfigurationElement element)
{

return (element as Period).Id;
}


public override ConfigurationElementCollectionType CollectionType
{

get { return ConfigurationElementCollectionType.BasicMap; }
}


protected override string ElementName
{

get { return "period"; }
}
}


public class Period:ConfigurationElement
{
[ConfigurationProperty(
"name")]
public string Name
{

get { return (string)this["name"]; }
}
[ConfigurationProperty(
"id")]
public int Id
{

get { return (int)this["id"]; }
}
}
}

  這段程序運行沒問題,意外的是在第一個period節點上,它的Name屬性是什么呢,一直以來我都弄錯了,可能還有許多人也弄錯了。正確答案是String.Empty,而不是null。而且即使我在ConfigurationProperty加上DefaultValue=null,Name返回的依然是String.Empty。

  在我看來,null應該用于表示一種在我們預料之外的狀態,包括傳入的參數和返回的結果。比如一段從數據庫讀取記錄的代碼,如果執行中出現異常,則返回null,和正常執行后未找到任何匹配記錄的情況截然不同。同理,對于節點的name屬性未定義的情況,顯然也不同于定義了name=”"的情況,返回null是合理的。而ConfigurationElement不允許string屬性為null,可能有某方面的考慮,但這應該是個Bug,因為它返回值含混不清,是對string空值的誤解。

  請再來看另一個例子:如果這個返回值變成了參數的情況。在實際軟件中,讀取XML數據源時,通常要進行架構驗證,比如像下面這樣:

 
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add(targetNamespace,
"postlist.xsd"); //targetNamespace是傳入方法的參數
settings.ValidationType = ValidationType.Schema;
XmlReader reader
= XmlReader.Create("postlist.xml", settings);

  這段代碼有一個問題,它未判斷targetNamespace的是Null還是String.Empty。對于XmlSchemaSet的Add方法,targetNamespace參數如果為null,則表示使用xsd文件中定義的命名空間,而如果傳入String.Empty,XmlSchemaSet依然認為,是我們手動指定了一個空的命名空間,而這完全不是我們想要的。

  如果在xsd文件中定義了targetNamespace,我們手動指定的namespace必須與之一致,否則將拋出異常。而xsd文件中的targetNameSpace屬性,你可以不加它,但加了就不允許空值。換句話說,我們傳入的為String.Empty時targetNameSpace參數時,xsd中只要有targetNamespace的定義,程序必然出錯。下圖也是上面代碼執行的結果,和傳null值的情況有天壤之別。

image

  我們不能怪罪寫Xml驗證的人太馬虎,微軟一個內部框架中就有這個問題。歸根究底,是.Net Framework對null的誤用,導致了我們的誤解。

  首先,ConfigurationElement類對返回值的誤用,屬性值該是null卻返回String.Empty。

  其次,XmlSchemaSet類的Add方法有一定問題,應該提供一個重載函數,不需要傳入為null的targetNamespace參數,直接用xsd文件中的對應屬性。 不但方便,也減少出錯的機會。當然,它正確的區分了null和empty的含義,這點還是值得稱贊的。

  連.Net Framework對null使用上都有如此的問題,我們寫程序時,應該更小心翼翼,對付這個難以捉摸、令人糾結的null。

0
0
 
標簽:.Net Null 編程
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()