laudy的博客

人变聪明容易,但想装糊涂可真难
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

【转】(翻译)反射的第一部分:发现和执行

Posted on 2009-06-03 18:52  laudy  阅读(197)  评论(0)    收藏  举报

何谓反射?


 

 

 

 

反射就是在运行的时候发现对象的相关信息。根据这些信息可以动态的执行对象的方法以及获取对象的属性所储存的值。使用.NET Framework编写的代码是自动反射的,或者说是自我描述的。之所以可以反射,是通过编译后产生的元数据来做到的。因此,你可以在你的程序中使用反射来查找托管代码中的类型(包括类的名称,方法以及参数)和与其相关的信息这其中包括执行被发现的代码。你也可以在程序运行的时候使用反射来创建,编译和运行代码。


 

 

反射的使用场所


 

 

有关反射最明显的例子就是类型或对象浏览器。想起Visual Studio .NET对象浏览器了吧。该工具可以显示程序集暴露的类,类的方法以及方法的参数等等(例如图一所示)。在过去,对我们有用的这些信息当中的一部分是通过COM类型库获取的。在.NET里,这些有用的程序数据完全可以从程序集本身来获取。所有的.NET程序集都是自我描述的,也就是说,它们包含着(它们的)类型的元数据,你可以通过查找元数据来了解一个特定的对象。
ArticleImage.gif

图一:Visual Studio .NET 对象浏览器

 

 


 

 

环顾.NET Framework框架和其各种不同的工具,你会很快的发现.NET本身在大量的使用反射。编译器在使用反射,其它的命名空间如:Remoting也在使用等等。如果你也喜欢,那么你肯定想知道:在我的程序中该如何使用反射呢?

 

 

有一点是毋庸置疑的,反射并不可能解决与你日常操作相关的业务程序的问题。比如:你不会使用Reflection命名空间像使用System.IO或者System.Data命名空间一样。但是,一旦你对反射为何物有一个清晰的认识,不久的将来,你将会意识到在很多特定的场合下应该使用反射。事实上,下面的描述将是使用反射的很好的场所:

 

 

建立在反射基础上的API帮助参考,自定义的元数据类型浏览器,关注于某一对象的代码测试工具产品,建立一个表单,允许用户执行代码像使用Web Serviceasmx文件一样,集中处理代码中出现的异常,记日志,在你任何需要的时候报告,为了使用户可以自由地连接部件(类似于销售渠道)而延迟绑定工作流程序。

 

 

从这个短小的清单中,你已经可以看到,反射给开发者们提供了很大的空间。让我们以最简单的方式来学习反射的相关知识吧。


 

 

反射的基础知识


 

 

当使用反射时,你的代码将会与一个基本的设计思路相吻合。首先,你需要加载一个程序集的元数据,然后在其中寻找你感兴趣的类型,最后,你将显示所得到的信息或者直接执行一个找到的类型。让我们关注一下这些在代码里是如何实现的。

 

 

首先,你必须在一个已知的类型或程序集上创建类Reflection.Assembly的一个实例,在这里不是使用一个构造器而是使用类Assembly的静态的方法:Load来实现的。该方法存在着一系列的版本,对于我们这个例子来说,我们只需要关心如下两个方法:

 

 

l         public static Assembly Load(string);

 

 

l         public static Assembly LoadFrom(string);

 

 

这两个方法都返回类Assembly的一个实例。Assembly.Load需要一个字符窜类型的参数,该参数表示要加载的程序集的名称(在当前的应用程序域内)。举个例子,如果你想创建一个简单的窗体程序,然后列举已经加载的程序集,这些程序集当中就有“System.Drawing,这是在窗体引擎中用到的程序集的名称(也是命名空间),因此要得到该程序集的实例,你就需要编写如下的代码:


 

 

 

Assembly myAssembly = Assembly.Load("System.Drawing");


你可以添加你自己要加载的程序集。也就是说,你的代码里可以读取已加载程序集的反射信息,做到这一步,只需要用你自己的程序集的名字替换上面方法Load里的System.Drawing

 

 

Assembly.LoadFrom以类似的方式工作,但是这个方法需要传递一个包含.NET程序集路径和名字的字符窜类型的参数。LoadFrom给了你使用任意.NET程序集的更好的选择,不仅仅是加载当前应用程序域内的程序集。如下所示:


string path = @"C:\WINNT\Microsoft.NET\Framework\v1.1.4322\System.Drawing.dll";
Assembly myAssembly 
= Assembly.LoadFrom(path);


C#开发人员也可以使用关键字typeof获取程序集,该运算符通过传递一个类型参数来获取该类型的System.Type对象,返回的对象里提供了访问其所在的程序集的途径,如下所示:


 

Assembly otherAssembly = typeof(System.Data.DataRow).Assembly;

 


当然,.NET框架也提供了另外一个有用方法,GetType。该方法可以用在所有.NET对象上。有关其例子,如下:


DataTable dt = new DataTable();
Assembly otherAssembly 
= dt.GetType().Assembly;



 

 

 

到这里,你已经了解了很多创建Reflection.Assembly实例的方法,让我们考虑一下,如何深入到程序集内部,查找其包含的相关信息。为了做这件事,你将会大量的使用System.Type类,它被用来表示所有的.NET类型(类,枚举,数组等)。你将会使用Assembly的一个实例返回一个Type实例数组,它包含了给定的程序集中所有的类型。举例说明,假设你有如下的一个控制台程序:



 

 

 

在这个程序集里定义了两个类型:testClass类和testEnum枚举。为了能够访问这两个类型,你需要调用Assembly.GetTypes,它将会以数组的方式返回程序集中定义的所有类型。如下面的例子,把下面的代码添加到之前的Main方法里,


Assembly myAssembly = Assembly.Load("Basics");
Type[] types 
= myAssembly.GetTypes();
foreach(Type type in types)
{
    
//do something
}



 

 

 

你现在已经通过myAssembly.GetTypes获取了程序集中所有类型的引用并且可以在这些获取的类型中进行迭代操作。在你的代码里,你已经可以使用这些类型对象了。你可以添加如下的代码,来输出类的名字:


 

foreach(Type type in types)
{
    
if(type.IsClass)
        Console.WriteLine(type.Name);
}


到目前,你已经成功的加载了某个程序集,在其包含的类型中进行迭代,并且显示了程序集中所有类的名字。这里已经展示了使用反射跟程序集中的元数据打交道所用到的最基本的思路。该文章剩下来的部分将会深层次的向你展示反射中的类如何做更多的事情。


 

 

反射的命名空间和类


 

 

.NET框架里有两个命名空间与反射相关,System.ReflectionSystem.Reflection.EmitSystem.Reflection命名空间中包含着与类型的发现和执行相关的对象,而System.Reflection.Emit命名空间中则包含着在运行时动态的产生代码的相关对象(有关这一部分内容会在另一篇文章中重点描述)。

 

 


发现反射:搜索和过滤


 

 

在该文章的前面,你学会了如何加载一个程序集和获取对其中类型的访问。事实上,对于给定的.NET程序集,均可以使用Assembly.GetTypes获取其中所有的类型。该方法将会返回存在于给定的程序集中的全局和公共的类型(依赖于你的.NET安全模式或上下文)。举个例子,下面的代码在其自己的基础上创建了一个Assembly实例并且在其包含的类型中迭代。

using System;
using System.Reflection;
namespace Basics
{
    
public class testClass
    
{
        
public static void Main()
        
{
            Assembly myAssembly 
= Assembly.Load("Basics");
            Type[] types 
= myAssembly.GetTypes();
            
foreach(Type type in types)
            
{
                Console.WriteLine(
"Type:{0}",type.Name);
            }

            Console.ReadLine();
        }

    }

}


对于给定的程序集,循环搜索其暴露的所有公共类型,看上去是个不错的主意。但是如果你的程序集很大,或者你只是想关注于特定的类型比如:构造器和属性,那么又该怎么办呢?如果你曾经关注过.NET框架的类库,你就会知道一个程序集中可能包含着非常多的类型。你可能并不需要知道每一个类型,你可能更喜欢通过搜索或者过滤来找到特定的类型。值得高兴的是,System.Type里提供了一系列的方法用于访问,搜索,过滤给定的程序集中特定的类型。


 

 

直接访问


 

 

直接访问一个给定的类型就说明,你已经知道并开始查找此种类型。可能你的程序只查找用户感兴趣的那些类型,或者可能从开始你就知道应该查找哪些特定的类型。在任何一种情况下,System.Type类已经提供了一系列可以直接访问特定类型的方法。类似于GetConstructorGetMethodGetPropertyGetEvent的方法允许你锁定特定的类型。示例如下,假如你有如下的类:


 

 

 

public class SomeClass
{
        
public SomeClass()
        
{}
        
public SomeClass(int someValue)
        
{}
        
public SomeClass(int someValue,int someOtherValue)
        
{}
        
public void SomeMethod()
        
{}
}


这个类有三个空的构造器和一个空方法。现在假设你想访问只有一个参数的那个构造器,为此你就需要使用Type类的GetConstructor方法,该方法允许你给它传递一个类型为Type的对象数组,该数组将被用于匹配构造器声明的参数。当执行的时候,此方法将找到这样一个构造器,该构造器的签名与定义参数中的数组相匹配。GetConstructor方法将返回一个ConstructorInfo对象供你使用(可能你会调用该构造器创建一个实例)。举例说明,你首先会创建类似于下下面的参数数组。


 

Type[] ts = {typeof(Int32)};


最后,你将会在你的Type对象上调用GetConstructor方法,如下所示:


 

 

 

ConstructorInfo ci = typeof(SomeClass).GetConstructor(ts);


同样,Type.GetMethod方法提供了在给定的对象上直接访问方法的途径。这个方法将返回一个MethodInfo实例供你使用。最简单的版本可以把给定的方法的名字当作参数传递给GetMethod,如下所示:


 

 

 

MethodInfo mi = typeof(SomeClass).GetMethod("SomeMethod");


你可能会有疑问,如果我在一个类里有两个方法,它们具有相同的名字不同的签名,那会怎样呢?很好,在之前的例子里,你将会得到一个不明确匹配的异常(System.Reflection.AmbiguousMatchException异常错误),不过,这里还有很多直接访问方法的版本,这些将会使你更准确地得到特定的类型。对于GetConstructor方法,除了以对象数组作为参数的方式来筛选相应的构造器以外,还可以依据指定使用的一套规则等筛选你需要的构造器。你可以把这一套模式应用到直接访问你的类型中特定的属性或事件中。

 

 


过滤

 

 


System.Type
类也提供了一些方法,用于把包含在一个类里或者其它的类型里的特定的类型过滤到一个集合中。如GetConstructors方法,GetMethods方法,GetProperties方法和GetEvents方法均允许你以数组的方式返回所有给定的类型或者通过使用过滤条件只返回特定的类型集合。

 

 

一个典型的过滤器包括设置好的BindingFlagsBindingFlags表示过滤的条件,你可以使用枚举类BindingFlags的值表示一些如:public或者non-public类型。你也可以用这些标志来表示静态的成员,甚至你可以组合这些BindingFlags来缩小你的查找范围。假设你有如下一个简单的类。


 

 

 

public class OtherClass
{
        
public void OtherMethod()
        
{}
        
public static void OtherStaticMethod()
        
{}
        
public static void AnotherStaticMethod()
        
{}
}


在这里,你会看到,它有三个公共的方法,其中的两个为静态的方法。假设你想通过反射找出类OtherClass里的所有公共的静态方法,你将会调用GetMethods方法,并为其传递枚举

 

 

BindingFlags的值类似于bindingAttr这样的参数。如下的代码将会返回OtherClass类中公共静态的方法集合。

 

 


 

MethodInfo[] mis = typeof(OtherClass).GetMethods(BindingFlags.Public | BindingFlags.Static);

 

 

 

你可以使用相同的技术用于返回私有的类型(你需要有相应的权限)。为此,你可以使用BindingFlags的枚举值BindingFlags.NonPublic。这个也可以与BindingFlags.Instance一起使用,用于返回所有的私有的实例成员。这一套方式也可以被用于返回构造器,属性和事件。

 

 


搜索

 

 


正如你猜测的那样,搜索与过滤非常的类似,它们真正的不同之处在于搜索是通过System.Type的一个抽象的方法FindMembers来做到的。与其调用一个过滤器比如:GetEvents,倒不如你使用FindMembers,然后给它传递一个MemberType.Events的值作为memberType的参数,这样会让你拥有更多的灵活性。如果你需要一个自定义的过滤器,它并不包含某一明确的类型,你就可以使用FindMembers的参数来满足不同的搜索需要。接下来是个例子。

 

 

需要说明的是,下面的类定义了三个字段,二个私有的和一个共有的。

 

 



 

 

 

 

public class AnotherClass
{
        
private int myPrvField1 = 15;
        
private string myPrvField2 = "Some private field";
        
public decimal myPubField1 = 1.03m;
}



 

 

 

假设你需要使用Type类的方法FindMembers来获取AnotherClass实例上的私有字段,并且显示它们的值。你将会通过设置FindMembers的参数memberType的值为MemberType.Field,同时你还要设置BindingFlags的值,这样FindMembers将会返回与搜索条件匹配的MemberInfo对象数组。下面的代码片断展示了这样的例子:

FieldInfo fi;
AnotherClass ac 
= new AnotherClass();
MemberInfo[] memInfo 
= ac.GetType().FindMembers(MemberTypes.Field,BindingFlags.NonPublic | BindingFlags.Instance,null,null);
foreach(MemberInfo m in memInfo)
{
    fi 
= m as FieldInfo;
    
if(fi != null)
    
{
        Console.WriteLine(
"{0} of value:{1}",fi.Name,fi.GetValue(ac));
    }

}


一旦你发现目标成员变量,就可以把它们转化为真正的FieldInfo对象,这样你就可以查找它们的值了。