Effective C#之Item 42:Utilize Attributes to Simplify Reflection

  rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Item 42: Utilize Attributes to Simplify Reflection

使用特性进行简单的反射

When you build systems that rely on reflection, you should define custom attributes for the types, methods, and properties you intend to use to make them easier to access. The custom attributes indicate how you intended the method to be used at runtime. Attributes can test some of the properties of the target. Testing these properties minimizes the likelihood of mistyping that can happen with reflection.

当你构建基于反射的系统时,你应该为打算要使用的类型、方法和属性定义自己的特性,使它们更容易访问。自定义特性暗示了你希望这些方法如何在运行时被使用。特性可以检测目标的一些属性。检测这些属性,使得可能会发生在反射上面的类型错误的可能性减到最小。

Suppose you need to build a mechanism to add menu items and command handlers to a running software system. The requirements are simple: Drop an assembly into a directory, and the program will find out about it and add new menu items for the new command. This is one of those jobs that is best handled with reflection: Your main program needs to interact with assemblies that have not yet been written. The new add-ins also don't represent a set of functionality that can be easily encoded in an interface.

假设你需要构建一个机制来对运行的软件系统添加菜单项和命令句柄。要求很简单:将一个程序集拖到一个目录,程序将会发现它,为新命令加入新的菜单项。这是使用反射可以很好的被处理的工作之一:你的主程序需要和还没有被编写的程序集进行交互。新的插件也不代表某些功能集合,它很容易用接口编码。

Let's begin with the code you need to create the add-in framework. You need to load an assembly using the Assembly.LoadFrom() function. You need to find the types that might provide menu handlers. You need to create an object of the proper type. Type.GetConstructor() and ConstructorInfo.Invoke() are the tools for that. You need to find a method that matches the menu command event handler signature. After all those tasks, you need to figure out where on the menu to add the new text, and what the text should be.

让我们从创建该插件框架需要的代码开始。你需要使用Assembly.LoadFrom()方法来载入程序集,需要找到可能提供菜单句柄的类型,需要创建恰当类型的一个对象。Type.GetConstructor()ConstructorInfo.Invoke()就是做这些工作的工具。你需要找到这样的方法:和菜单命令事件句柄的签名一致。在所有这些任务之后,你需要计算出在菜单的哪个地方添加新的文本以及文本应该是什么。

Attributes make many of these tasks easier. By tagging different classes and event handlers with custom attributes, you greatly simplify your task of finding and installing those potential command handlers. You use attributes in conjunction with reflection to minimize the risks described in Item 43.

特性使得这些任务更容易。通过将自定义特性标记到不同的类和事件句柄上,可以极大的简化这些工作:发现并安装潜在的命令句柄。将特性和反射一起使用,可以减少在Item43里面描述的风险。

The first task is to write the code that finds and loads the add-in assemblies. Assume that the add-ins are in a subdirectory under the main executable directory. The code to find and load the assemblies is simple:

第一个任务就是编写发现和加载插件程序集的代码。假设插件位于主执行程序目录下的一个子目录里面。查找和加载程序集的代码很简单:

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

Next, you need to replace that last comment with the code that finds the classes that implement command handlers and installs the handlers. After you load an assembly, you can use reflection to find all the exported types in an assembly. Use attributes to figure out which exported types contain command handlers and which methods are the command handlers. An attribute class marks the types that have command handlers:

接下来,你需要替换最后一行注释,查找并安装实现了命令句柄的类。加载这些程序集之后,可以使用反射找到所有在程序集里暴露的类型。使用特性来计算出哪个暴露的类型包含了命令句柄以及哪些方法是命令句柄。特性类会对含有事件句柄的类型进行标记:

  1. // Define the Command Handler Custom Attribute:
  2. [AttributeUsage( AttributeTargets.Class )]
  3. public class CommandHandlerAttribute : Attribute
  4. {
  5.   public CommandHandlerAttribute( )
  6.   {
  7.   }
  8. }
  9.  

This attribute is all the code you need to write to mark each command. Always mark an attribute class with the AttributeUsage attribute; it tells other programmers and the compiler where your attribute can be used. The previous example states that the CommandHandlerAttribute can be applied only to classes; it cannot be applied on any other language element.

为了标记每个命令,所有你要写的代码就是上面要标记的特性。永远使用AttributeUsage特性对特性类进行标记;它告诉其他程序员以及编译器你的特性能用在哪里。前面的例子阐述了CommandHandlerAttribute仅仅能够被用在类上;它不能被用在其他语言元素上面。

You call GetCustomAttributes to determine whether a type has the CommandHandlerAttribute. Only those types are candidates for add-ins:

通过调用GetCustomAttributes来决定一个类型是否具有CommandHandlerAttribute。只有这些类型才是插件的候选者:

  1. // Find all the assemblies in the Add-ins directory:
  2. string AddInsDir = string.Format( "{0}/Addins", Application.StartupPath);
  3. string[] assemblies = Directory.GetFiles( AddInsDir, "*.dll" );
  4. foreach ( string assemblyFile in assemblies )
  5. {
  6.   Assembly asm = Assembly.LoadFrom( assemblyFile );
  7.   // Find and install command handlers from the assembly.
  8.   foreach( System.Type t in asm.GetExportedTypes( ))
  9.   {
  10.     if (t.GetCustomAttributes( typeof( CommandHandlerAttribute ), false ).Length > 0 )
  11.     {
  12.       // Found the command handler attribute on this type.
  13.       // This type implements a command handler.
  14.       // configure and add it.
  15.     }
  16.     // Else, not a command handler. Skip it.
  17.   }
  18. }
  19.  

Now let's add another new attribute to find command handlers. A type might easily implement several command handlers, so you define a new attribute that add-in authors will attach to each command handler. This attribute will include parameters that define where to place menu items for new commands. Each event handler handles one specific command, which is located in a specific spot on the menu. To tag a command handler, you define an attribute that marks a property as a command handler and declares the text for the menu item and the text for the parent menu item. The DynamicCommand attribute is constructed with two parameters: the command text and the text of the parent menu. The attribute class contains a constructor that initializes the two strings for the menu item. Those strings are also available as read/write properties:

现在添加一个新的特性来查找命令句柄。一个类型可以很容易的就实现多个命令句柄,因此你要定义一个新的特性,插件的作者可以将该特性绑定到每个命令句柄上面。该特性包含了一些参数,这些参数定义了将新命令的菜单项放到哪里。每个事件句柄只处理一个特定的命令,会被加载到菜单的一个特定位置。为了标记一个命令句柄,你需要定义一个特性,可以将属性标记为命令句柄,声明菜单项的名字以及父菜单的名字。DynamicCommand特性由两个参数组成:命令的名字和父菜单的名字。该特性类包含了对菜单名字字符串进行初始化的构造函数。这些字符串同时也可以作为读/写属性。

  1. [AttributeUsage( AttributeTargets.Property ) ]
  2. public class DynamicMenuAttribute : System.Attribute
  3. {
  4.   private string _menuText;
  5.   private string _parentText;
  6.  
  7.   public DynamicMenuAttribute( string CommandText, string ParentText )
  8.   {
  9.     _menuText = CommandText;
  10.     _parentText = ParentText;
  11.   }
  12.  
  13.   public string MenuText
  14.   {
  15.     get { return _menuText; }
  16.     set { _menuText = value; }
  17.   }
  18.  
  19.   public string ParentText
  20.   {
  21.     get { return _parentText; }
  22.     set { _parentText = value; }
  23.   }
  24. }

 

This attribute class is tagged so that it can be applied only to properties. The command handler must be exposed as a property in the class that provides access to the command handler. Using this technique simplifies finding the command handler code and attaching it to the program at startup.

该特性类被打上了标记,因此它只能被应用在属性上面。命令句柄应该在提供对命令句柄进行访问的类里面以属性的方式进行暴露。使用该技术简化了对命令句柄的查找,也简化了在程序启动时的帮定。

Now you create an object of that type, find the command handlers, and attach them to new menu items. You guessed it you use a combination of attributes and reflection to find and use the command handler properties:

现在你创建那个类型的一个对象,查找命令句柄,将它们绑定到一个新的菜单项上。你可以把特性和反射组合起来使用,用于查找和使用命令句柄属性,对对象进行猜测:

  1. // Expanded from the first code sample:
  2. // Find the types in the assembly
  3. foreach( Type t in asm.GetExportedTypes( ) )
  4. {
  5.   if (t.GetCustomAttributes( typeof( CommandHandlerAttribute ), false).Length > 0 )
  6.   {
  7.     // Found a command handler type:
  8.     ConstructorInfo ci = t.GetConstructor( new Type[0] );
  9.     if ( ci == null ) // No default ctor
  10.       continue;
  11.     object obj = ci.Invoke( null );
  12.     PropertyInfo [] pi = t.GetProperties( );
  13.  
  14.     // Find the properties that are command
  15.     // handlers
  16.     foreach( PropertyInfo p in pi )
  17.     {
  18.       string menuTxt = "";
  19.       string parentTxt = "";
  20.       object [] attrs = p.GetCustomAttributes( typeof ( DynamicMenuAttribute ), false );
  21.       foreach ( Attribute at in attrs )
  22.       {
  23.         DynamicMenuAttribute dym = at as DynamicMenuAttribute;
  24.         if ( dym != null )
  25.         {
  26.           // This is a command handler.
  27.           menuTxt = dym.MenuText;
  28.           parentTxt = dym.ParentText;
  29.           MethodInfo mi = p.GetGetMethod();
  30.           EventHandler h = mi.Invoke( obj, null ) as EventHandler;
  31.           UpdateMenu( parentTxt, menuTxt, h );
  32.         }
  33.       }
  34.     }
  35.   }
  36. }
  37.  
  38. private void UpdateMenu( string parentTxt, string txt, EventHandler cmdHandler )
  39. {
  40.   MenuItem menuItemDynamic = new MenuItem();
  41.   menuItemDynamic.Index = 0;
  42.   menuItemDynamic.Text = txt;
  43.   menuItemDynamic.Click += cmdHandler;
  44.  
  45.   //Find the parent menu item.
  46.   foreach ( MenuItem parent in mainMenu.MenuItems )
  47.   {
  48.     if ( parent.Text == parentTxt )
  49.     {
  50.       parent.MenuItems.Add( menuItemDynamic );
  51.       return;
  52.     }
  53.   }
  54.   // Existing parent not found:
  55.   MenuItem newDropDown = new MenuItem();
  56.   newDropDown.Text = parentTxt;
  57.   mainMenu.MenuItems.Add( newDropDown );
  58.   newDropDown.MenuItems.Add( menuItemDynamic );
  59. }

 

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特性对类型进行标记。正如你在这里看到的,将一个特性绑定到某个元素的时候,一般会从名字里面省略Attribute

  1. [ CommandHandler ]
  2. public class CmdHandler
  3. {
  4.   // Implementation coming soon.
  5. }
  6.  

Inside the CmdHandler class, you add a property to retrieve the command handler. That property should be tagged with the DynamicMenu attribute:

CmdHandler类内部,添加了一个属性来获得命令句柄。该属性应该用DynamicMenu特性来标记:

  1. [DynamicMenu( "Test Command""Parent Menu" )]
  2. public EventHandler CmdFunc
  3. {
  4.   get
  5.   {
  6.     if ( theCmdHandler == null )
  7.       theCmdHandler = new System.EventHandler (this.DynamicCommandHandler);
  8.     return theCmdHandler;
  9.   }
  10. }
  11.  
  12. private void DynamicCommandHandler( object sender, EventArgs args )
  13. {
  14.   // Contents elided.
  15. }
  16.  

That's it. This example shows you how you can utilize attributes to simplify programming idioms that use reflection. You tagged each type that provided a dynamic command handler with an attribute. That made it easier to find the command handlers when you dynamically loaded the assembly. By applying AttributeTargets (another attribute), you limit where the dynamic command attribute can be applied. This simplifies the difficult task of finding the sought types in a dynamically loaded assembly: You greatly decrease the chance of using the wrong types. It's still not simple code, but it is a little more palatable than without attributes.

就是这了。这个例子向你展示了该如何利用特性,可以简化使用反射的编程习惯。使用特性,对每个类型进行标记,让该类型提供动态命令句柄。当你动态加载程序集的时候,这使得查找命令句柄更容易。通过应用AttributeTargets (另一个特性),可以限制动态命令特性可以应用在哪里。这样就简化了这个困难的工作:在动态加载的程序集里面查找原始类型。大大的减少了使用错误类型的机会。这仍然不是简单的代码,但是这和没有特性相比,已经很不错了。

Attributes declare your runtime intent. Tagging an element with an attribute indicates its use and simplifies the task of finding that element at runtime. Without attributes, you need to define some naming convention to find the types and the elements that will be used at runtime. Any naming convention is a source of human error. Tagging your intent with attributes shifts more responsibilities from the developer to the compiler. The attributes can be placed only on a certain kind of language element. The attributes also carry syntactic and semantic information.

特性声明了你在运行时的意图。使用特性对一个元素进行标记,暗示了它的用处,同时,简化了在运行时寻找该元素的任务。没有特性的话,你需要定义一些名称转换来查找在运行时会被使用的类型和元素。任何名称的转换都是人为错误的源头。使用特性来标记你的意图,将更多的职责从程序员转给了编译器。特性仅仅能被使用在一部分特性语言元素上面。特性同时也带有了语法和语义信息。

You use reflection to create dynamic code that can be reconfigured in the field. Designing and implementing attribute classes to force developers to declare the types, methods, and properties that can be used dynamically decreases the potential for runtime errors. That increases your chances of creating applications that will satisfy your users.

你可以使用反射来创建可以在文件里被重新配置的动态代码。设计并实现特性类来强制开发者声明这些类型、方法和属性,它们可以被动态的使用,这样就减少了运行时的潜在错误。这让你增加了创建让用户满足的应用程序的机会。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值