您的位置:首页 > Web前端

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

2008-12-29 16:21 344 查看
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:

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

}

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:

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

[AttributeUsage( AttributeTargets.Class )]

public class CommandHandlerAttribute : Attribute

{

public CommandHandlerAttribute( )

{

}

}

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。只有这些类型才是插件的候选者:// 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.

}

}

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特性由两个参数组成:命令的名字和父菜单的名字。该特性类包含了对菜单名字字符串进行初始化的构造函数。这些字符串同时也可以作为读/写属性。[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; }

}

}

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:

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

}

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。[ CommandHandler ]

public class CmdHandler

{

// Implementation coming soon.

}

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

在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.

}

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.

你可以使用反射来创建可以在文件里被重新配置的动态代码。设计并实现特性类来强制开发者声明这些类型、方法和属性,它们可以被动态的使用,这样就减少了运行时的潜在错误。这让你增加了创建让用户满足的应用程序的机会。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: