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