與命令式編程相比,申明式編程可以用更簡單,更清楚的方法來描述軟件的 行為。申明式編程就是說用申明來定義程序的行為,而不是寫一些指令。在C#裡 ,也和其它大多數語言一樣,你的大多數程序都是命令式的:在程序中寫一個方 法來定義行為。在C#中,你在編程時使用特性就是申明式編程。你添加一個特性 到類,屬性,數據成員,或者是方法上,然後.Net運行時就會為你添加一些行為 。這樣申明的目的就是簡單易用,而且易於閱讀和維護。
讓我們以一個 你已經使用過的例子開始。當你寫你的第一個ASP.Net Web服務時,向導會生成 這樣的代碼:
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}
VS.net的Web服務向導添加了[WebMethod]特性到HelloWorld()方 法上,這就定義了HelloWorld是一個web方法。ASP.net運行時會為你生成代碼來 響應這個特性。運行時生成的Web服務描述語言(WSDL)文檔,也就是包含了對 SOAP進行描述的文檔,調用HelloWorld方法。ASP.net也支持運行時發送SOAP請 求HelloWorld方法。另外,ASP.net運行時動態的生成HTML面頁,這樣可以讓你 在IE裡測試你的新Web服務。而這些全部是前面的WebMethod特性所響應的。這個 特性申明了你的意圖,而且運行時確保它是被支持的。使用特性省了你不少時間 ,而且錯誤也少了。
這並不是一個神話,ASP.net運行時使用反射來斷定 類裡的哪些方法是web服務,當它們發現這些方法時,ASP.net運行時就添加一些 必須的框架代碼到這些方法上,從而使任何添加了這些代碼的方法成為web方法 。
[WebMethod] 特性只是.Net類庫眾多特性之一,這些特性可能幫助你 更快的創建正確的程序。有一些特性幫助你創建序列化類型(參見原則25)。正如 你在原則4裡看到的,特性可以控制條件編譯。在這種情況以下其它一些情況下 ,你可以使用申明式編程寫出你所要的更快,更少錯誤的代碼。
你應該 使用.Net框架裡自帶的一些特性來申明你的意圖,這比你自己寫要好。因為這樣 花的時間少,更簡單,而且編譯器也不會出現錯誤。
如果預置的特性不 適合你的需求,你也可以通過定義自己的特性和使用反射來使用申明式編程結構 。做為一個例子,你可以創建一個特性,然而關聯到代碼上,讓用戶可以使用這 個特性來創建默認可以排序的類型。一個例子演示了如何添加這個特性,該特性 定義了你想如何在一個客戶集合中排序:
[DefaultSort( "Name" )]
public class Customer
{
public string Name
{
get { return _name; }
set { _name = value; }
}
public decimal CurrentBalance
{
get { return _balance; }
}
public decimal AccountValue
{
get
{
return calculateValueOfAccount();
}
}
}
DefaultSort特性,Nane屬性,這就暗示了任何Customer的集合 應該以客戶名字進行排序。DefaultSort特性不是.Net框架的一部份,為了實現 它,你創建一個DefaultSortAttribute類:
[AttributeUsage( AttributeTargets.Class |
AttributeTargets.Struct )]
public class DefaultSortAttribute : System.Attribute
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public DefaultSortAttribute( string name )
{
_name = name;
}
}
同樣,你還必須寫一些代碼,來對一個集合運行排 序,而該集合中的元素是添加了DefaultSort特性的對象。你將用到反射來發現 正確的屬性,然後比較兩個不同對象的屬性值。一個好消息是你只用寫一次這樣 的代碼。
下一步,你要寫一個實現了IComparer接口的類。(在原則26中 會詳細的充分討論比較。) ICompare有一個CompareTo()方法來比較兩個給定類 型的對象,把特性放在實現了IComparable的類上,就可以定義排序順序了。構 造函數對於通用的比較,可以發現默認的排序屬性標記,而這個標記是基於已經 比較過的類型。Compare方法對任何類型的兩個對象進行排序,使用默認的排序 屬性:
internal class GenericComparer : IComparer
{
// Information about the default property:
private readonly PropertyDescriptor _sortProp;
// Ascending or descending.
private readonly bool _reverse = false;
// Construct for a type
public GenericComparer( Type t ) :
this( t, false )
{
}
// Construct for a type
// and a direction
public GenericComparer( Type t, bool reverse )
{
_reverse = reverse;
// find the attribute,
// and the name of the sort property:
// Get the default sort attributes on the type:
object [] a = t.GetCustomAttributes(
typeof( DefaultSortAttribute ),false );
// Get the PropertyDescriptor for that property:
if ( a.Length > 0 )
{
DefaultSortAttribute sortName = a[ 0 ] as DefaultSortAttribute;
string name = sortName.Name;
// Initialize the sort property:
PropertyDescriptorCollection props =
TypeDescriptor.GetProperties( t );
if ( props.Count > 0 )
{
foreach ( PropertyDescriptor p in props )
{
if ( p.Name == name )
{
// Found the default sort property:
_sortProp = p;
break;
}
}
}
}
}
// Compare method.
int IComparer.Compare( object left,
object right )
{
// null is less than any real object:
if (( left == null ) && ( right == null ))
return 0;
if ( left == null )
return -1;
if ( right == null )
return 1;
if ( _sortProp == null )
{
return 0;
}
// Get the sort property from each object:
IComparable lField =
_sortProp.GetValue( left ) as IComparable;
IComparable rField =
_sortProp.GetValue( right ) as IComparable;
int rVal = 0;
if ( lField == null )
if ( rField == null )
return 0;
else
return - 1;
rVal = lField.CompareTo( rField );
return ( _reverse ) ? -rVal : rVal;
}
}
這個通用的比較 對任何Customers 集合可以進行排序,而這個Customers是用DefaultSort特性申 明了的:
CustomerList.Sort( new GenericComparer(
typeof( Customer )));
實現GenericComparer的代碼利用了一些 高級的技術,使用反射(參見原則43)。但你必須寫一遍這樣的代碼。從這個觀點 上看,你所要做的就是添加空上屬性到其它任何類上,然而你就可以對這些對象 的集合進行能用的排序了。如果你修改了DefaultSort特性的參數,你就要修改 類的行為。而不用修改所有的算法。
這種申明式習慣是很有用的,當一 個簡單的申明可以說明你的意圖時,它可以幫助你避免重復的代碼。再參考 GenericComparer類,你應該可以為你創建的任何類型,寫一個不同的(而且是是 直接了當的)排序算法。這種申明式編程的好處就是你只用寫一次能用的類型, 然後就可以用一個簡單的申明為每個類型創建行為。關鍵是行為的改變是基於單 個申明的,不是基於任何算法的。GenericComparer可以在任何用DefaultSort特 性修飾了的類型上工作,如果你只須要在程序裡使用一兩次排序功能,就按常規 簡單的方法寫吧。然而,如果你的程序對於同樣的行為,可能須要在幾十個類型 上實現,那麼能用的算法以及申明式的解決方案會省下很多時間,而且在長時間 的運行中也是很有力的。你不應該為WebMethod特性寫代全部的代碼,你應該把 這一技術展開在你自己的算法上。原則42裡討論了一個例子:如何使用特性來建 立一個附加命令句柄。其它的例子可能還包括一些在定義附加包建立動態的web UI面頁時的其它內容。
申明式編程是一個很有力的工具,當你可以使用 特性來表明你的意圖時,你可以通過使用特性,來減少在大量類似的手寫算法中 出現邏輯錯誤的可能。申明式編程創建了更易於閱讀,清晰的代碼。這也就意味 著不管是現在還是將來,都會少出現錯誤。如果你可以使用.Net框架裡定義的特 性,那就直接使用。如果不能,考慮選擇創建你自己的特性,這樣你可以在將來 使用它來創建同樣的行為。
返回教程目錄