当我们在构建基于反射的系统时,我们应该为期望使用的类型、方法和属性创建定制特性,从而简化对它们的访问。定制特性描述了我们对于“方法如何在运行时被使用”所做的期望,它可以测试目标对象的某些属性,可以将“伴随反射发生的拼写错误”的可能性降至最低。
当我们需要通过反射来访问某种类型的对象时,需要加载某个dll文件中的程序集,然后遍历程序集中的类型,由于程序集中的类型会非常多,如何确定遍历的类型是我们需要的类型呢?如果在类型中,添加一个定制特性,那么我们可以通过对类型特性的判断,来确定这个类型是否是我们需要的类型。
来看下面的代码。
代码
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(
11 typeof( CommandHandlerAttribute ), false ).Length > 0 )
12 {
13 // Found the command handler attribute on this type.
14 // This type implements a command handler.
15 // configure and add it.
16 }
17 // Else, not a command handler. Skip it.
18 }
19 }
上面代码中说明,如果指定类型中包含CommandHandlerAttribute特性,那么这个类型就是我们期望的类型。
定制特性除了可以标识期望类型外,它还有其他作用,我们可以利用特性构造函数来设置业务中使用的一些数据,虽然这样做有时不是很合适。
来看以下的代码。
代码
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,
8 string ParentText )
9 {
10 _menuText = CommandText;
11 _parentText = ParentText;
12 }
13
14 public string MenuText
15 {
16 get { return _menuText; }
17 set { _menuText = value; }
18 }
19
20 public string ParentText
21 {
22 get { return _parentText; }
23 set { _parentText = value; }
24 }
25 }
上面代码定义了一个定制特性,其中包含了两个属性,下面的代码说明了如何使用这个特性。
代码
1 // Find the types in the assembly
2 foreach( Type t in asm.GetExportedTypes( ) )
3 {
4 if (t.GetCustomAttributes(
5 typeof( CommandHandlerAttribute ), false).Length > 0 )
6 {
7 // Found a command handler type:
8 ConstructorInfo ci =
9 t.GetConstructor( new Type[0] );
10 if ( ci == null ) // No default ctor
11 continue;
12 object obj = ci.Invoke( null );
13 PropertyInfo [] pi = t.GetProperties( );
14
15 // Find the properties that are command
16 // handlers
17 foreach( PropertyInfo p in pi )
18 {
19 string menuTxt = "";
20 string parentTxt = "";
21 object [] attrs = p.GetCustomAttributes(
22 typeof ( DynamicMenuAttribute ), false );
23 foreach ( Attribute at in attrs )
24 {
25 DynamicMenuAttribute dym = at as
26 DynamicMenuAttribute;
27 if ( dym != null )
28 {
29 // This is a command handler.
30 menuTxt = dym.MenuText;
31 parentTxt = dym.ParentText;
32 MethodInfo mi = p.GetGetMethod();
33 EventHandler h = mi.Invoke( obj, null )
34 as EventHandler;
35 UpdateMenu( parentTxt, menuTxt, h );
36 }
37 }
38 }
39 }
40 }
41
42 private void UpdateMenu( string parentTxt, string txt,
43 EventHandler cmdHandler )
44 {
45 MenuItem menuItemDynamic = new MenuItem();
46 menuItemDynamic.Index = 0;
47 menuItemDynamic.Text = txt;
48 menuItemDynamic.Click += cmdHandler;
49
50 //Find the parent menu item.
51 foreach ( MenuItem parent in mainMenu.MenuItems )
52 {
53 if ( parent.Text == parentTxt )
54 {
55 parent.MenuItems.Add( menuItemDynamic );
56 return;
57 }
58 }
59 // Existing parent not found:
60 MenuItem newDropDown = new MenuItem();
61 newDropDown.Text = parentTxt;
62 mainMenu.MenuItems.Add( newDropDown );
63 newDropDown.MenuItems.Add( menuItemDynamic );
64 }
一般来说,是不会在定制特性中包含业务数据的,上述代码只是说明的作用。
特性实际上声明的是我们在运行时的意图,在一个元素上标记特性可以描述它的用途,以及降低在运行时查找该元素的难度,如果没有特性,我们就需要定义某种命名约定,来支持在运行时查找类型及其元素,命名约定是错误的源头,用特性来表达意图可以将更多的责任由开发人员转移到编译器上。
我们通常使用反射来创建可以被重新配置的动态代码。通过设计和实现特性类,强制开发人员用它们来声明可以被动态使用的类型、方法和属性,可以减少应用程序的运行时错误。