Effective C#之Item 43:Don't Overuse 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 43: Don't Overuse Reflection

勿滥用反射

Building binary components sometimes means utilizing late binding and reflection to find the code with the particular functionality you need. Reflection is a powerful tool, and it enables you to write software that is much more dynamic. Using reflection, an application can be upgraded with new capabilities by adding new components that were not available when the application was deployed. That's the upside.

有时候,构建二进制组件意味着利用晚绑定和反射来寻找你需要的有特定功能的代码。反射是一个强有力的工具,它让你能编写更具有动态性的软件。使用反射,通过添加新的组件,应用程序就能更新新的功能,而这些组件在应用程序部署时是不可用的。这就是反射积极的一面。

With this flexibility comes increased complexity, and with increased complexity comes increased chance for many problems. When you use reflection, you circumvent C#'s type safety. Instead, the Invoke members use parameters and return values typed as System.Object. You must make sure the proper types are used at runtime. In short, using reflection makes it much easier to build dynamic programs, but it is also much easier to build broken programs. Often, with a little thought, you can minimize or remove the need for reflection by creating a set of interface definitions that express your assumptions about a type.

伴随着灵活性,也相应的增加了复杂性,同时,随着复杂性的增加,也就相应的增加了出现问题的机会。当你使用反射时,就要被C#的类型安全性纠缠。相反,调用(Invoke)的成员使用的参数以及返回的值类型都是System.Object。你要确保运行时使用了正确的类型。简而言之,使用反射使得构建动态程序变得更简单,但是,使用反射的同时也很容易导致失败的程序。通常要稍微考虑一下就知道,通过创建一系列的接口,用以描述关于某个类型的假设,那么,你可以使得对反射的需求最小,甚至移除它。

Reflection gives you the capability to create instances of objects, invoke members on those objects, and access data members in those objects. Those sound like normal everyday programming tasks. They are. There is nothing magic about reflection: It is a means of dynamically interacting with other binary components. In most cases, you don't need the flexibility of reflection because other alternatives are more maintainable.

反射给了你创建对象实例的能力,调用它们的方法,访问它们的数据成员。这些听起来像日常的程序任务。确实是的,反射并没有什么神奇的:它是动态的和其他二进制组件进行交互的一种方式。在多数情况下,你不需要反射的灵活性,因为其它的替代方法更具可维护性。

Let's begin with creating instances of a given type. You can often accomplish the same result using a class factory. Consider this code fragment, which creates an instance of MyType by calling the default constructor using reflection:

让我们从创建一个给定类型的实例开始。通常,你可以使用类工厂来得到同样的结果。考虑下面的代码段,它通过使用反射,调用默认的构造函数创建了MyType的一个实例:

  1. // Usage:Create a new object using reflection:
  2. Type t = typeof( MyType );
  3. MyType obj = NewInstance( t ) as MyType;
  4.  
  5. // Example factory function, based on Reflection:
  6. object NewInstance( Type t )
  7. {
  8.   // Find the default constructor:
  9.   ConstructorInfo ci = t.GetConstructor( new Type[ 0 ] );
  10.   if ( ci != null )
  11.     // Invoke default constructor, and return
  12.     // the new object.
  13.     return ci.Invoke( null );
  14.  
  15.   // If it failed, return null.
  16.   return null;
  17. }
  18.  

The code examines the type using reflection and invokes the default constructor to create the object. If you need to create a type at runtime without any previous knowledge of the type, this is the only option. This is brittle code that relies on the presence of a default constructor. It still compiles if you remove the default constructor from MyType. You must perform runtime testing to catch any problems that arise. A class factory function that performed the same operations would not compile if the default constructor was removed:

这段代码检查了使用反射的类型,调用默认的构造函数创建了对象实例。如果你需要在运行时创建一个类型,而又事先对该类型一无所知,那么这是你唯一的选择。依赖于默认的构造函数的存在,这样的代码是脆弱的。如果你从MyType里面移除默认的构造函数,仍然能够通过编译。你应该执行运行时检查来捕获生成的任何问题。能执行同样操作的类工厂方法,在默认构造函数被移除的情况下,将不能通过编译:

  1. public MyType NewInstance( )
  2. {
  3.   return new MyType();
  4. }
  5.  

You should create static factory functions instead of relying on reflection to instantiate objects. If you need to instantiate objects using late binding, create factory functions and tag them as such with attributes (see Item 42).

你应该创建静态工厂方法来取代反射来实例化对象。如果你需要使用晚绑定来实例化对象,创建工厂方法并且使用特性对它们进行标记(Item42)

Another potential use of reflection is to access members of a type. You can use the member name and the type to call a particular function at runtime:

反射的另一个潜在的应用是访问类型的成员。你可以是用成员名字和类型,在运行时调用特定的功能:

  1. // Example usage:
  2. Dispatcher.InvokeMethod( AnObject, "MyHelperFunc" );
  3.  
  4. // Dispatcher Invoke Method:
  5. public void InvokeMethod ( object o, string name )
  6. {
  7.   // Find the member functions with that name.
  8.   MemberInfo[] myMembers = o.GetType( ).GetMember( name );
  9.   foreach( MethodInfo m in myMembers )
  10.   {
  11.     // Make sure the parameter list matches:
  12.     if ( m.GetParameters( ).Length == 0 )
  13.       // Invoke:
  14.      m.Invoke( o, null );
  15.   }
  16. }

 

Runtime errors are lurking in the previous code. If the name is typed wrong, the method won't be found. No method will be called.

上面的代码有潜在的运行时错误。如果名称被输入错了,方法就找不到了。将没有任何方法会被调用。

It's also a simple example. Creating a more robust version of InvokeMethod would need to check the types of all proposed parameters against the list of all parameters returned by the GetParameters() method. That code is lengthy enough and ugly enough that I did not even want to waste the space to show it to you. It's that bad.

这是一个简单的例子,要创建更健壮的InvokeMethod的版本的话,将需要检查所有提出的参数的类型,以及由GetParameters()方法返回的所有参数的列表。这写代码相当长,相当丑,我甚至不打算浪费纸张展示给你看了,那糟糕透了。

The third use of reflection is accessing data members. The code is similar to accessing member functions:

对反射的第三种用法是访问数据成员。代码和访问成员方法很类似:

  1. // Example usage:
  2. object field = Dispatcher.RetrieveField ( AnObject, "MyField" );
  3.  
  4. // elsewhere in the dispatcher class:
  5. public object RetrieveField ( object o, string name )
  6. {
  7.   // Find the field.
  8.   FieldInfo myField = o.GetType( ).GetField( name );
  9.   if ( myField != null )
  10.     return myField.GetValue( o );
  11.   else
  12.     return null;
  13. }
  14.  

As with the method invocation, using reflection to retrieve a data member involves querying the type for a field with a name that matches the requested field. If one is found, the value can be retrieved using the FieldInfo structure. This construct is rather common in the framework. DataBinding makes use of reflection to find the properties that are the targets of binding operation. In those cases, the dynamic nature of data binding outweighs the possible costs.

和方法调用一样,使用反射来获得数据成员,涉及到使用名称来查询类型,使其和需要想匹配。如果找到了一个,那么使用FieldInfo结构就能获得该值。该结构体在框架里面是相当常见的。数据绑定利用反射来查找作为绑定操作目标的属性。在那些情况下,数据绑定的动态特征将比可能的花费要重要。

So, if reflection is such a painful process, you need to look for better and simpler alternatives. You have three options. The first is interfaces. You can define interfaces for any contract that you expect classes or structs to implement (see Item 19). That would replace all the reflection code with a few far clearer lines of code:

因此,如果反射是这样痛苦的过程的话,你就需要寻找更好更简单的替代片。你有3个选择。第一个选择是接口。你可以使用接口来定义任何你希望类或者结构体来实现的约定(Item19)。这样,使用一小点清晰的代码行就能替换所有的放射代码:

  1. IMyInterface foo = obj as IMyInterface;
  2. if ( foo != null)
  3. {
  4.   foo.DoWork( );
  5.   foo.Msg = "work is done.";
  6. }

 

If you combine interfaces with a factory function tagged with an attribute, almost any system you thought deserved a solution based on reflection gets much more simple:

如果你将接口与用特性进行标记过的工厂方法相结合,那么,几乎任何你原来想用基于反射的方法来解决的系统,都可以变得更简单:

  1. public class MyType : IMyInterface
  2. {
  3.   [FactoryFunction]
  4.   public static IMyInterface CreateInstance( )
  5.   {
  6.     return new MyType( );
  7.   }
  8.  
  9.   #region IMyInterface
  10.   public string Msg
  11.   {
  12.     get
  13.     {
  14.       return _msg;
  15.     }
  16.     set
  17.     {
  18.       _msg = value;
  19.     }
  20.   }
  21.   public void DoWork( )
  22.   {
  23.     // details elided.
  24.   }
  25.   #endregion
  26. }

 

Contrast this code with the reflection-based solution shown earlier. Even these simple examples have glossed over some of the weakly typed issues common to all the reflection APIs: The return values are all typed as objects. If you want to get the proper type, you need to cast or convert the type. Those operations could fail and are inherently dangerous. The strong type checking that the compiler provides when you create interfaces is much clearer and more maintainable.

将上面的代码和前面看到的基于反射的解决方案相比较。甚至这写简单的例子就掩盖了一些在所有反射API里面会出现的常见的弱类型问题:返回值都是object类型。如果你希望得到正确的类型,就需要强制转换类型。那些操作可能会失败并存在固有的危险性。在你创建接口时,编译器提供的强类型检查,是相当清晰的,也相当易维护。

Reflection should be used only when the invocation target can't be cleanly expressed using an interface. .NET data binding works with any public property of a type. Limiting it to an interface definition would greatly limit its reach. The menu handler sample allows any function (either instance or static) to implement the command handler. Using an interface would limit that functionality to instance methods only. Both FxCop and NUnit (see Item 48) make extensive use of reflection. They use reflection because the nature of the problems they address are best handled using it. FxCop examines all your code to evaluate it against a set of known rules. That requires reflection. NUnit must call test code you've written. It uses reflection to determine what code you've written to unit test your code. An interface cannot express the full set of methods used to test any code you might write. NUnit does use attributes to find tests and test cases to make its job easier (see Item 42).

只有当被调用的目标在使用接口不能被清晰的表述时,才应该使用反射。.Net数据绑定与类型的任何公开属性一起工作。将其限制在接口里,将大大的限制它的使用。菜单句柄的例子允许任何方法(实例或者静态)实现命令句柄。使用接口将限制那个功能,使其只能作用于实例方法。FxCop NUnit (Item 48)都充分利用了反射。它们使用反射是因为它们描述的问题本身使用反射来解决最好。FxCop检查你所有的代码,用一系列已知的规则进行评估。这都需要反射。NUnit应该调用你已经编写的代码。它使用反射来决定你写得哪些代码要进行单元测试。接口不能表述要用来进行测试的完整的方法集合。NUnit使用了特性来发现测试以及测试用例来使它的工作更简单(Item42)

When you can factor out the methods or properties that you intend to invoke using interfaces, you'll have a cleaner, more maintainable system. Reflection is a powerful late-binding mechanism. The .NET Framework uses it to implement data binding for both Windows- and web-based controls. However, in many less general uses, creating code using class factories, delegates, and interfaces will produce more maintainable systems.

当你使用接口就可以将希望调用的方法或者属性提炼出来时,将会得到一个清晰的易维护的系统。反射是一个强有力的晚绑定机制。.Net框架使用它来为基于Windowsweb的控件实现数据绑定。然而,在很多缺少通用性的使用上,通过类工厂,委托以及接口创建代码,将生成更易维护的系统。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值