反射Reflection in C#
在这篇文章中,我们将用C#示例讨论一下反射。在C#中反射提供了可以描述程序集、模块和类型的对象。你可使用反射自动创建一个类型的实例,绑定类型到已存的对象上,或是从现有对象获取类型并调用其方法或访问其字段和属性。在这篇文章中,我们基本是讨论反射是什么,如何执行反射,最后,我们将探讨什么时候使用反射。
什么是反射?
当你想要确定或是检查程序集的内容的时候,就需要反射了;在这里,内容就是指程序集的元数据,像程序集中的方法是什么,程序集中的属性是什么,它们是public还是private,等等。
例如,反射的最大实现之一就是Visual Studio本身。假如,在VS中,我们创建了一个String类的对象,当我们输入obj.时VS智能感应会显示出该对象所有的属性,方法,字段等等,如下图所示那样。由于反射的存在,这成为了可能。
所以,基本上说来,反射会检查程序庥并显示程序集中的元数据。现在,我希望你应该理解了反射的定义。让我们继续进行并理解如何在C#中实现反射。
如何实现反射
现在我们将写一个简单的例子来实现反射。
第一步,创建一个名称为ReflectionDemo的console应用程序。在这个console应用程序中,我们会添加一个名称为SomeClassLibrary的类库。
一旦添加了类库项目,你的方案就像下面所示那样:
如上图所示,类库项目以一个类名叫Class1.cs进行创建。现在,修改Class1.cs类文件如下所示。如类文件中所示那样,我们已经创建了一些private和public字段,一些private和public属性,还有一些private和public方法。
1 using System; 2 namespace SomeClassLibrary 3 { 4 public class Class1 5 { 6 public int X; 7 private int Y; 8 public int P1 { get; set; } 9 private int P2 { get; set; } 10 public void Method1() 11 { 12 Console.WriteLine("Method1 Invoked"); 13 } 14 private void Method2() 15 { 16 Console.WriteLine("Method2 Invoked"); 17 } 18 } 19 }
将代码填写完后,生成类库项目。并且,一旦生成类库项目一个程序集(扩展名.dll)将会在项目文件的bin->Debug文件中进行创建完成,如下图片所示:
SomeClassLibrary.dll程序集的创建位置是 D:\Projects\ReflectionDemo\SomeClassLibrary\bin\Debug
现在,从方案中移除类库项目。在类加项目上右击,然后点移除选项,如下所示
一旦你点击了移除项,会弹出对话窗,点Yes就可以移除项目了。一旦你移除了类库项目,你的方案中将会只包含console应用,如下所示:
浏览SomeClassLibrary程序集的属性、方法和变量
现在,我们需要做的是使用反射显示SomeClassLibrary程序集的属性、方法和变量。实现反射有三个步骤,具体步骤如下:
1、step1:导入反射的命名空间
2、step2:获取对象的类型
3、step3:浏览对象的元数据
再强调一下,首先,我们需要导入反射的命名空间,然后我们需要获取对象的类型,一旦我们获取了对象的类型,我们就可以浏览元数据,比如浏览方法、属性、变量等等。所以,让我们看一下这三个步骤的具体实现。
step1:导入反射的命名空间
using System.Reflection;
step2:获取对象的类型
首先,我们需要获取程序集的引用。为了获取程序集的引用,我们需要使用Assembly.Loadfile方法,并且提供程序集的路径(需要提供dll文件存在的准确路径位置),如下面这样:
var MyAssembly = Assembly.LoadFile(@”D:\Projects\ReflectionDemo\SomeClassLibrary\bin\Debug\SomeClassLibrary.dll”);
一旦获得了程序集的引用,下一步就是获得类的引用。这意味着一旦获取了程序集引用,就需要从该程序集引用中获取类的引用。这么做的话,需要在程序集引用中调用GetType方法;对于这个get type方法来说,我们需要提供类的完全限定名称,例如:namespace.class名称如下所示:
var MyType = MyAssembly.GetType(“SomeClassLibrary.Class1”);
一旦获取了对象的类型,就需要创建一个类型的实例。为了动态创建一个实例,我们需要使用Activator.CreateInstance方法,并且我们需要向该方法传递类型对象,如下所示:
dynamic MyObject = Activator.CreateInstance(MyType);
一旦对象创建了,下一步就是获取类的类型。为了获取类的类型,我们要使用GetType方法,如下所示:
Type parameterType = MyObject.GetType();
step3:浏览对象的元数据
在这一步骤中,我们要浏览程序集的元数据。为了获取所有的public成员,我们要用到GetMembers;为了获取所有的方法函数,我们要用到GetMethods;为了获取所有变量或字段,我们要用到GetFields;为了获取程序集的所有属性,我们要用到GetProperties.
这些有用的方法函数如下:
1、GetFields():返回当前System.Type的所有公共字段
2、GetProperties():返回当前System.Type的所有公共属性
3、GetMethods():返回当前System.Type的所有公共方法
4、GetMembers():返回当前System.Type的所有公共成员
下面是完整的示例代码:
1 using System; 2 //Step1: Import the Reflection namespace 3 using System.Reflection; 4 5 namespace ReflectionDemo 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 //Browse the Properties, Methods, variables of SomeClassLibrary Assembly 12 13 //Step2: Get the type 14 15 //Get the Assembly Reference 16 var MyAssembly = Assembly.LoadFile(@"D:\Projects\ReflectionDemo\SomeClassLibrary\bin\Debug\SomeClassLibrary.dll"); 17 18 //Get the Class Reference 19 var MyType = MyAssembly.GetType("SomeClassLibrary.Class1"); 20 21 //Create an instance of the type 22 dynamic MyObject = Activator.CreateInstance(MyType); 23 24 //Get the Type of the class 25 Type parameterType = MyObject.GetType(); 26 27 //Step3: Browse the Metadata 28 29 //To Get all Public Fields/variables 30 Console.WriteLine("All Public Fields"); 31 foreach (MemberInfo memberInfo in parameterType.GetFields()) 32 { 33 Console.WriteLine(memberInfo.Name); 34 } 35 36 //To Get all Public Methods 37 Console.WriteLine("\nAll Public Methods"); 38 foreach (MemberInfo memberInfo in parameterType.GetMethods()) 39 { 40 Console.WriteLine(memberInfo.Name); 41 } 42 43 //To Get all Public Properties 44 Console.WriteLine("\nAll Public Properties"); 45 foreach (MemberInfo memberInfo in parameterType.GetProperties()) 46 { 47 Console.WriteLine(memberInfo.Name); 48 } 49 50 Console.ReadKey(); 51 } 52 } 53 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Reflection; 6 7 namespace ReflectionDemo 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 var MyAssembly = Assembly.LoadFile(@"C:\Users\Administrator\Documents\Visual Studio 2010\Projects\ReflectionDemo\SomeClassLibrary\bin\Debug\SomeClassLibrary.dll"); 14 15 var MyType = MyAssembly.GetType("SomeClassLibrary.Class1"); 16 17 dynamic MyObject = Activator.CreateInstance(MyType); 18 19 Type parameterType = MyObject.GetType(); 20 21 Console.WriteLine( "All Public Fields"); 22 foreach (MemberInfo memberInfo in parameterType.GetFields()) 23 { 24 Console.WriteLine( memberInfo.Name); 25 } 26 27 Console.WriteLine("\n All Public Methods"); 28 foreach (MemberInfo memberInfo in parameterType.GetMethods()) 29 { 30 Console.WriteLine(memberInfo.Name); 31 } 32 33 Console.WriteLine( "\nAll Public Properties"); 34 foreach (MemberInfo memberInfo in parameterType.GetProperties()) 35 { 36 Console.WriteLine(memberInfo.Name); 37 } 38 } 39 } 40 }
从上面可以看到,在所有的方法函数中也同时获取到object类的方法(即:Equals GetHashCode GetType ToString),这是因为object是所有类的超级父类。get_P1和set_P1是公共属性P1的setter和getter方法。所以,这就是你在C#中如何使用反射来提取程序集的元数据。
在C#中使用反射显示类型详细信息的示例:
基本上来说,我们想做的是,一旦我们获取到了Type,然后我们就想要显示类名、完全限定名和名称空间的名字。为了达到这个目标,我们需要调用Name,FullName和Namespace属性,具体如下所示:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Reflection; 6 7 namespace ReflectionDemo 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 var MyAssembly = Assembly.LoadFile(@"C:\Users\Administrator\Documents\Visual Studio 2010\Projects\ReflectionDemo\SomeClassLibrary\bin\Debug\SomeClassLibrary.dll"); 14 15 var MyType = MyAssembly.GetType("SomeClassLibrary.Class1"); 16 17 Console.WriteLine( "Full Name = {0}",MyType.FullName); 18 Console.WriteLine("Just the Class Name = {0}",MyType.Name); 19 Console.WriteLine("Just the Namespace Name = {0}",MyType.Namespace); 20 21 //dynamic MyObject = Activator.CreateInstance(MyType); 22 23 //Type parameterType = MyObject.GetType(); 24 25 //Console.WriteLine( "All Public Fields"); 26 //foreach (MemberInfo memberInfo in parameterType.GetFields()) 27 //{ 28 // Console.WriteLine( memberInfo.Name); 29 //} 30 31 //Console.WriteLine("\n All Public Methods"); 32 //foreach (MemberInfo memberInfo in parameterType.GetMethods()) 33 //{ 34 // Console.WriteLine(memberInfo.Name); 35 //} 36 37 //Console.WriteLine( "\nAll Public Properties"); 38 //foreach (MemberInfo memberInfo in parameterType.GetProperties()) 39 //{ 40 // Console.WriteLine(memberInfo.Name); 41 //} 42 } 43 } 44 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Reflection; 6 7 namespace ReflectionDemo 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 var MyAssembly = Assembly.LoadFile(@"C:\Users\Administrator\Documents\Visual Studio 2010\Projects\ReflectionDemo\SomeClassLibrary\bin\Debug\SomeClassLibrary.dll"); 14 15 var MyType = MyAssembly.GetType("SomeClassLibrary.Class1"); 16 17 Console.WriteLine( "Full Name = {0}",MyType.FullName); 18 Console.WriteLine("Just the Class Name = {0}",MyType.Name); 19 Console.WriteLine("Just the Namespace Name = {0}",MyType.Namespace); 20 21 //dynamic MyObject = Activator.CreateInstance(MyType); 22 23 //Type parameterType = MyObject.GetType(); 24 25 //Console.WriteLine( "All Public Fields"); 26 //foreach (MemberInfo memberInfo in parameterType.GetFields()) 27 //{ 28 // Console.WriteLine( memberInfo.Name); 29 //} 30 31 //Console.WriteLine("\n All Public Methods"); 32 //foreach (MemberInfo memberInfo in parameterType.GetMethods()) 33 //{ 34 // Console.WriteLine(memberInfo.Name); 35 //} 36 37 //Console.WriteLine( "\nAll Public Properties"); 38 //foreach (MemberInfo memberInfo in parameterType.GetProperties()) 39 //{ 40 // Console.WriteLine(memberInfo.Name); 41 //} 42 } 43 } 44 }
上面就是在C#中如何使用反射提取程序集的类型信息。现在,让我们看看在C#中使用反射的其他一些好处。
在C#中使用反射动态调用方法:
反射的一个很好的特性是它会检查程序集的元数据,前面已经提到过。使用反射的另一个好处是,我们可以在C#中调用程序集的成员。所以说,如果你记得我们在程序类库中已经定义的一个公共方法Method1,那我们这次就要使用反射来调用这个方法。
为了使用反射来调用程序集中的方法,我们要用到InvokeMember函数,如下所示:
InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args):此方法调用指定的成员,使用指定的绑定约束并匹配指定的参数列表。此函数方法返回一个object,此object代表调用成员的返回值。此方法的具体参数如下:
1、name :string name 包含了要调用的构造函数、方法、属性或字段成员的名称等;在本示例中是Method1
2、invokeAttr:由一个或多个System.Reflection.BindingFlags组成的位掩码,用于指定如何执行搜索。访问符可能是BindingFlags之一,例如Public,NonPublic,Private,InvokeMethod,GetField,等等。不需要指定查找类型。如果省略了查找类型,则使用BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static。
3、binder:定义一组属性并启用绑定的对象,这可能涉及选择重载方法、强制参数类型以及通过反射调用成员-或-使用System.Type.DefaultBinder的空引用。请注意,要成功调用变量参数的方法重载需要显式定义一个System.Reflection.Binder对象。上面示例中,我们传递的是null值。
4、target:这个参数用来调用指定的成员。在上面 的示例中,这个对象是MyObject
5、args:包含了参数的数组,这个参数是要传递给要调用的成员。在我们的示例中没有包含任何参数,所以说我们传递是null值。
注意:这种调用方法完全在运行时完成。如果该方法在运行时存在,它将调用该方法,否则将引发异常。这意味着C#中的Reflection在运行时完成方法的完整动态调用。
C#中使用反射动态调用方法的示例:
1 using System; 2 //Step1: Import the Reflection namespace 3 using System.Reflection; 4 5 namespace ReflectionDemo 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 //Browse the Properties, Methods, variables of SomeClassLibrary Assembly 12 13 //Step2: Get the type 14 15 //Get the Assembly Reference 16 var MyAssembly = Assembly.LoadFile(@"D:\Projects\ReflectionDemo\SomeClassLibrary\bin\Debug\SomeClassLibrary.dll"); 17 18 //Get the Class Reference 19 var MyType = MyAssembly.GetType("SomeClassLibrary.Class1"); 20 21 //Create an instance of the type 22 dynamic MyObject = Activator.CreateInstance(MyType); 23 24 //Get the Type of the class 25 Type parameterType = MyObject.GetType(); 26 27 //Step3: Browse the Metadata 28 29 //To Get all Public Fields/variables 30 Console.WriteLine("All Public Members"); 31 foreach (MemberInfo memberInfo in parameterType.GetMembers()) 32 { 33 Console.WriteLine(memberInfo.Name); 34 } 35 36 Console.WriteLine("\nInvoking Method1"); 37 38 parameterType.InvokeMember("Method1", 39 BindingFlags.Public | 40 BindingFlags.InvokeMethod | 41 BindingFlags.Instance, 42 null, MyObject, null 43 ); 44 45 Console.ReadKey(); 46 } 47 } 48 }
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var MyAssembly = Assembly.LoadFile(@"C:\Users\Administrator\Documents\Visual Studio 2010\Projects\ReflectionDemo\SomeClassLibrary\bin\Debug\SomeClassLibrary.dll"); 6 7 var MyType = MyAssembly.GetType("SomeClassLibrary.Class1"); 8 9 //创建类型的实例 10 dynamic MyObject = Activator.CreateInstance(MyType); 11 //获取类的类型 12 Type paramterType = MyObject.GetType(); 13 14 Console.WriteLine("All Public Members"); 15 foreach (MemberInfo memberInfo in paramterType.GetMembers()) 16 { 17 Console.WriteLine(memberInfo.Name); 18 } 19 20 Console.WriteLine( "\n Invoking Method1"); 21 22 paramterType.InvokeMember("Method1", 23 BindingFlags.Public | 24 BindingFlags.InvokeMethod | 25 BindingFlags.Instance, 26 null, MyObject, null); 27 }
C#中反射的实时用途有哪些?
1、如果你正在创建类似VS编辑器这样的应用程序,你希望使用智能显示来展示内部的细节,比如对象的元数据;
2、在单元测试中,有时我们需要调用私有方法来测试私有成员是否正常工作。
3、有时我们希望将属性、方法和程序集引用转储到一个文件中,或者可能在屏幕上显示它。
4、在C#中使用反射也可以实现后期绑定。我们可以使用反射来动态创建一个类型的实例,在编译时我们没有任何关于该类型的信息。因此,反射使我们能够使用编译时不可用的代码。
5、考虑一个例子,我们有两个接口的替代实现。您希望允许用户使用配置文件选择一个或另一个。通过反射,您可以简单地从配置文件中读取要使用其实现的类的名称,并实例化该类的实例。这是使用反射的后期绑定的另一个示例。
注意:
反射用于查找程序集中的所有类型和/或动态调用程序集中的方法,这包括关于对象的类型、属性、方法和事件的信息。通过反射,我们可以动态地创建一个类型的实例,将该类型绑定到一个现有的对象,或者从一个现有的对象获取该类型并调用其方法或访问其字段和属性。