程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> Effective C#原則42:使用特性進行簡單的反射

Effective C#原則42:使用特性進行簡單的反射

編輯:關於C#

當你創建了一個與反射相關的系統時,你應該為你自己的類型,方法,以及 屬性定義一些自己的特性,這樣可以讓它們更容易的被訪問。自定義的特性標示 了你想讓這些方法在運行時如何被使用。特性可以測試一些目標對象上的屬性。 測試這些屬性可以最小化因為反射時可能而產生的類型錯誤。

假設你須 要創建一個機制,用於在運行時的軟件上添加一個菜單條目到一個命令句柄上。這個須要很簡單:放一個程序集到目錄裡,然後程序可以自己發現關於它的一些 新菜單條目以及新的菜單命令。這是利用反射可以完成的最好的工作之一:你的 主程序須要與一些還沒有編寫的程序集進行交互。這個新的插件同樣不用描述某 個集合的功能,因為這可以很好的用接口來完成編碼。

讓我們為創建一 個框架的插件來開始動手寫代碼吧。你須要通過Assembly.LoadFrom() 函數來加 載一個程序,而且要找到這個可能提供菜單句柄的類型。然後須要創建這個類型 的一個實例對象。接著還要找到這個實例對象上可以與菜單命令事件句柄的申明 相匹配的方法。完成這些任務之後,你還須要計算在菜單的什麼地方添加文字,以及什麼文字。

特性讓所有的這些任務變得很簡單。通過用自己定義的 特性來標記不同的類以及事件句柄,你可以很簡單的完成這些任務:發現並安裝 這些潛在的命令句柄。你可以使用特性與反射來協作,最小化一些在原則43中描 述的危險事情。

第一個任務就是寫代碼,發現以及加載插件程序集。假 設這個插件在主執行程序所在目錄的子目錄中。查找和加載這個程序集的代碼很 簡單:

// Find all the assemblies in the Add-ins directory:
string AddInsDir = string.Format( " {0}/Addins",
 Application.StartupPath );
string[] assemblies = Directory.GetFiles( AddInsDir, "*.dll" );
foreach ( string assemblyFile in assemblies )
{
 Assembly asm = Assembly.LoadFrom( assemblyFile );
 // Find and install command handlers from the assembly.
}

接下來,你須要 把上面最後一行的注釋替換成代碼,這些代碼要查找那些實現了命令句柄的類並 且要安裝這些句柄。加載完全程序集之後,你就可以使用反射來查找程序集上所 有暴露出來的類型,使用特性來標識出哪些暴露出來的類型包含命令句柄,以及 哪些是命令句柄的方法。下面是一個添加了特性的類,即標記了命令句柄類型:

// Define the Command Handler Custom Attribute:
[AttributeUsage( AttributeTargets.Class )]
public class CommandHandlerAttribute : Attribute
{
 public CommandHandlerAttribute( )
 {
 }
}

這個 特性就是你須要為每個命令標記的所有代碼。總是用AttributeUsage 特性標記 一個特性類,這就是告訴其它程序以及編譯器,在哪些地方這個特性可以使用。 前面這個例子表示CommandHandlerAttribute只能在類上使用,它不能應用在其 它語言的元素上。

你可以調用GetCustomAttributes來斷定某個類是否具 有CommandHandlerAttribute特性。只有具有該特性的類型才是插件的候選類型 :

// Find all the assemblies in the Add-ins directory:
string AddInsDir = string.Format( "{0}/Addins", Application.StartupPath);
string[] assemblies = Directory.GetFiles( AddInsDir, "*.dll" );
foreach ( string assemblyFile in assemblies )
{
 Assembly asm = Assembly.LoadFrom( assemblyFile );
 // Find and install command handlers from the assembly.
 foreach( System.Type t in asm.GetExportedTypes( ))
 {
  if (t.GetCustomAttributes (
   typeof( CommandHandlerAttribute ), false ).Length > 0 )
  {
   // Found the command handler attribute on this type.
   // This type implements a command handler.
    // configure and add it.
  }
  // Else, not a command handler. Skip it.
 }
}

現在,讓我們添加另一個 新的特性來查找命令句柄。一個類型應該可以很簡單的實現好幾個命令句柄,所 以你可以定義新的特性,讓插件的作者可以把它添加到命令句柄上。這個特性會 包含一參數,這些參數用於定義新的菜單命令應該放在什麼地方。每一個事件句 柄處理一個特殊的命令,而這個命令應該在菜單的某個特殊地方。為了標記一個 命令句柄,你要定義一個特性,用於標記一個屬性,讓它成為一個命令句柄,並 且申明菜單上的文字以及父菜單文字。DynamicCommand特性要用兩個參數來構造 :菜單命令文字以及父菜單的文字。這個特性類還包含一個構造函數,這個構造 函數用於為菜單初始化兩個字符串。這些內容同樣可以使用可讀可寫的屬性:

[AttributeUsage( AttributeTargets.Property ) ]
public class DynamicMenuAttribute : System.Attribute
{
  private string _menuText;
 private string _parentText;
  public DynamicMenuAttribute( string CommandText,
  string ParentText )
 {
  _menuText = CommandText;
   _parentText = ParentText;
 }
 public string MenuText
 {
  get { return _menuText; }
  set { _menuText = value; }
 }
 public string ParentText
 {
   get { return _parentText; }
  set { _parentText = value; }
 }
}

這個特性類已經做了標記,這樣它只能被應用到屬 性上。而命令句柄必須在類中以屬性暴露出來,用於提供給命令句柄來訪問。使 用這一技術,可以讓程序在啟動的時候查找和添加命令句柄的代碼變得很簡單。

現在你創建了這一類型的一個對象:查找命令句柄,以及添加它們到新 的菜單項中。你可以把特性和反射組合起來使用,用於查找和使用命令句柄屬性 ,對對象進行推測:

// Expanded from the first code sample:
// Find the types in the assembly
foreach( Type t in asm.GetExportedTypes( ) )
{
 if (t.GetCustomAttributes(
  typeof( CommandHandlerAttribute ), false).Length > 0 )
 {
  // Found a command handler type:
  ConstructorInfo ci =
   t.GetConstructor( new Type[0] );
  if ( ci == null ) // No default ctor
   continue;
  object obj = ci.Invoke( null );
  PropertyInfo [] pi = t.GetProperties( );
  // Find the properties that are command
  // handlers
  foreach( PropertyInfo p in pi )
  {
    string menuTxt = "";
   string parentTxt = "";
   object [] attrs = p.GetCustomAttributes(
    typeof ( DynamicMenuAttribute ), false );
   foreach ( Attribute at in attrs )
   {
     DynamicMenuAttribute dym = at as
      DynamicMenuAttribute;
    if ( dym != null )
     {
     // This is a command handler.
     menuTxt = dym.MenuText;
     parentTxt = dym.ParentText;
      MethodInfo mi = p.GetGetMethod();
     EventHandler h = mi.Invoke( obj, null )
      as EventHandler;
      UpdateMenu( parentTxt, menuTxt, h );
    }
   }
  }
 }
}
private void UpdateMenu( string parentTxt, string txt,
 EventHandler cmdHandler )
{
  MenuItem menuItemDynamic = new MenuItem();
  menuItemDynamic.Index = 0;
 menuItemDynamic.Text = txt;
  menuItemDynamic.Click += cmdHandler;
 //Find the parent menu item.
 foreach ( MenuItem parent in mainMenu.MenuItems )
  {
  if ( parent.Text == parentTxt )
  {
    parent.MenuItems.Add( menuItemDynamic );
   return;
   }
 }
 // Existing parent not found:
 MenuItem newDropDown = new MenuItem();
 newDropDown.Text = parentTxt;
 mainMenu.MenuItems.Add( newDropDown );
  newDropDown.MenuItems.Add( menuItemDynamic );
}

現在 你將要創建一個命令句柄的示例。首先,你要用CommandHandler 特性標記類型 ,正如你所看到的,我們習慣性的在附加特性到項目上時,在名字上省略 Attribute:

Now you'll build a sample command handler. First, you tag the type with the CommandHandler attribute. As you see here, it is customary to omit Attribute from the name when attaching an attribute to an item:

[ CommandHandler ]
public class CmdHandler
{
 // Implementation coming soon.
}

在CmdHandler 類裡面,你要添加一個屬性來取回命令句柄。這 個屬性應該用DynamicMenu 特性來標記:

[DynamicMenu( "Test Command", "Parent Menu" )]
public EventHandler CmdFunc
{
 get
 {
  if ( theCmdHandler == null )
   theCmdHandler = new System.EventHandler
    (this.DynamicCommandHandler);
   return theCmdHandler;
 }
}
private void DynamicCommandHandler(
 object sender, EventArgs args )
{
 // Contents elided.
}

就是這了。這個例子演 示了你應該如何使用特性來簡化使用反射的程序設計習慣。你可以用一個特性來 標記每個類型,讓它提供一個動態的命令句柄。當你動態的載入這個程序集時, 可以更簡單的發現這個菜單命令句柄。通過應用AttributeTargets (另一個特性 ),你可以限制動態命令句柄應用在什麼地方。這讓從一個動態加載的程序集上 查找類型的困難任務變得很簡單:你確定從很大程度上減少了使用錯誤類型的可 能。這還不是簡單的代碼,但比起不用特性,還算是不錯的。

特性可以 申明運行的意圖。通過使用特性來標記一個元素,可以在運行時指示它的用處以 及簡化查找這個元素的工作。如何沒有特性,你須要定義一些命名轉化,用於在 運行時來查找類型以及元素。任何命名轉化都會是發生錯誤的起源。通過使用特 性來標記你的意圖,就把大量的責任從開發者身上移到了編譯器身上。特性可以 是只能放置在某一特定語言元素上的,特性同樣也是可以加載語法和語義信息的 。

你可以使用反射來創建動態的代碼,這些代碼可以在實際運行中進行 配置。設計和實現特性類,可以強制開發者為申明一些類型,方法,以及屬性, 這些都是可以被動態使用的,而且減少潛在的運行時錯誤。也就是說,讓你增加 了創建讓用戶滿足的應用程序的機會。

返回教程目錄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved