自定義屬性可以用于對(duì)程序集中的元素進(jìn)行標(biāo)記和描述,并被編譯到.NET程序集中,成為其元數(shù)據(jù)的一部分。而從屬性和屬性值的讀取是對(duì).NET程序集元數(shù)據(jù)的讀取,這會(huì)用到反射機(jī)制。具體如何編寫自定義屬性和如何讀取屬性的例子在MSDN中有很多,不再冗述了。
3.屬性的應(yīng)用
屬性的以上的特性往往在設(shè)計(jì)一些框架時(shí)很有用:利用反射機(jī)制,作為屬性的元數(shù)據(jù)可以反過(guò)來(lái)在運(yùn)行期影響代碼的運(yùn)行配置項(xiàng),或者為特殊的操作方法作以屬性作標(biāo)記,以便在運(yùn)行時(shí)做特殊處理。屬性的另一個(gè)很有誘惑力的應(yīng)用是,可以用于構(gòu)建管理項(xiàng)目程序集的工具:屬性表現(xiàn)為某種注釋,而注釋內(nèi)容可以在編譯后從程序集中讀取出來(lái),從而可以通過(guò)屬性內(nèi)容的注釋和讀取來(lái)實(shí)現(xiàn)對(duì)程序集中各類型、方法的管理了。
3.1 NUnit中的屬性應(yīng)用
先看看屬性在框架設(shè)計(jì)中的應(yīng)用吧!典型的例子是NUnit。在NUnit的框架設(shè)計(jì)中將自定義屬性的特性、以及.NET的反射機(jī)制發(fā)揮得淋漓盡致。以一個(gè)簡(jiǎn)化了的測(cè)試案例(TestCase)為例:在測(cè)試時(shí),NUnit需要讓其中3種不同的函數(shù)依次運(yùn)行如下:
首先前運(yùn)行測(cè)試前的環(huán)境準(zhǔn)備函數(shù);然后是0~n個(gè)測(cè)試函數(shù);后是測(cè)試環(huán)境清理函數(shù)。熟悉NUnit的開(kāi)發(fā)者都知道,在NUnit的TestCase中分別使用[SetUp]、[Test]、[TearDown]屬性來(lái)進(jìn)行標(biāo)記。如下例:
//一個(gè)NUnit測(cè)試程序集中代碼
[SetUp]
public void Init()
//…
[TearDown]
public void Destroy()
//…
[Test]
public void TestXXX()
//…
NUnit框架在運(yùn)行時(shí)要從待測(cè)試程序集中讀取出上述函數(shù),并且要保證上述3種不同的函數(shù)以正確的先后順序被依次調(diào)用。NUnit是這樣實(shí)現(xiàn)的:
首先是開(kāi)發(fā)了一套屬性,用來(lái)標(biāo)記測(cè)試案例(TestCase)中各種函數(shù),如:[SetUp]、[Test]、[TearDown]。(NUnit的屬性標(biāo)記并不止用來(lái)標(biāo)記程序集中的函數(shù),但限于篇幅,這里只在先前作的簡(jiǎn)化環(huán)境中討論)
NUnit在運(yùn)行時(shí)利用反射機(jī)制運(yùn)行已經(jīng)被編譯成程序集的測(cè)試案例(TestCase)中的函數(shù)。NUnit框架中有一系列的函數(shù)來(lái)完成這項(xiàng)工作,這些函數(shù)只負(fù)責(zé)運(yùn)行測(cè)試案例程序集中特定屬性標(biāo)記所標(biāo)記的函數(shù)。如:InvokeSetUp()負(fù)責(zé)運(yùn)行標(biāo)記有[SetUp]的函數(shù);InvokeTestCase()負(fù)責(zé)運(yùn)行標(biāo)記有[Test]的函數(shù),即測(cè)試案例;InvokeTearDown()負(fù)責(zé)運(yùn)行標(biāo)記有[TearDown]的函數(shù)。然后NUnit利用這幾個(gè)InvokeXXX()函數(shù)的調(diào)用先后來(lái)保證這3種函數(shù)運(yùn)行的先后順序。
//From TemplateTestCase in NUnit.Core namespace
//用于執(zhí)行測(cè)試的Run函數(shù)
public override void Run(TestCaseResult testResult )
{
//…
try{
//…
InvokeSetUp();//首先運(yùn)行標(biāo)有[SetUp]標(biāo)記的函數(shù)
//…
InvokeTestCase();//然后是[Test]
//…
}
catch(…)
//…
finally {
//…
InvokeTearDown();//后是[TearDown]標(biāo)記的函數(shù)
//…
}
//…
}
而InvokeXXX()函數(shù)則利用反射機(jī)制運(yùn)行相關(guān)函數(shù),可以看看以下幾個(gè)代碼段:
//From TemplateTestCase in NUnit.Core namespace
private void InvokeSetUp()
{
MethodInfo method = FindSetUpMethod(fixture);//取得[SetUp]標(biāo)記的函數(shù)反射實(shí)例
if(method != null)
{
InvokeMethod(method, fixture);//運(yùn)行該函數(shù)
}
}
FindSetUpMethod(…)通過(guò)調(diào)用一個(gè)叫FindMethodByAttribute(…)的函數(shù),利用反射機(jī)制來(lái)獲得可調(diào)用該函數(shù)的MethodInfo,并后通過(guò)InvokeMethod(MethodInfo,…)來(lái)運(yùn)行。
//From Test class in NUnit.Core namespace
protected void InvokeMethod(MethodInfo method, object fixture)
{
if(method != null)
{
try
{
method.Invoke(fixture, null);//調(diào)用由method實(shí)例反射的方法或構(gòu)造函數(shù)
}
catch(…)
//…
}
}
仔細(xì)閱讀源碼可以看到因?yàn)镹Unit使用反射機(jī)制來(lái)運(yùn)行測(cè)試程序集中的測(cè)試案例,所以對(duì)[SetUp]、[Test]、[TearDown]函數(shù)的返回值、參數(shù)都有具體的要求,形成了一種規(guī)則耦合。這是為了方便反射實(shí)現(xiàn)、簡(jiǎn)化框架而作出的必要設(shè)計(jì)。
由NUnit可以看到.NET元數(shù)據(jù)擴(kuò)展中的自定義屬性在框架設(shè)計(jì)中的應(yīng)用,相信會(huì)有更多的框架類項(xiàng)目利用.NET自定義屬性的特性。下面是本文涉及的幾個(gè)類在NUnit中的關(guān)系(已經(jīng)作了簡(jiǎn)化)。