实例Csharp反射基础知识.docx
《实例Csharp反射基础知识.docx》由会员分享,可在线阅读,更多相关《实例Csharp反射基础知识.docx(21页珍藏版)》请在冰豆网上搜索。
实例Csharp反射基础知识
一、基本概念
反射:
反射是一个运行库类型发现的过程。
通过反射可以得到一个给定程序集所包含的所有类型的列表,这个列表包括给定类型中定义的方法、字段、属性和事件。
也可以动态的发现一组给定类支持的借口、方法的参数和其他相关信息如基类、命名空间、数据清单等。
二、命名空间
1.System.Reflection命名空间内的各类型
(1)Assembly通过它可以加载、了解和操纵一个程序集
(2)AssemblyName通过它可以找到大量隐藏在程序集的身份中的信息,如版本信息、区域信息等
(3)EventInfo事件的信息
(4)FieldInfo字段的信息
(5)MethodInfo方法的信息
(6)ParameterInfo参数的信息
(7)PropertyInfo属性的信息
(8)MemberInfo是抽象基类,为EventInfo、FieldInfo、MethodInfo、PropertyInfo等类型定义了公共的行为。
(9)Module用来访问带有多文件程序集的给定模块
2.System.Type类
System.Type支持的成员可以分为这样几类
(1)Is***用来检查一个类型的元数据,如IsAbstract、IsClass、IsValueType等等
(2)Get***用来从类型得到指定项目,如GetEvent()得到类型的一个指定的事件(EventInfo)。
另外,这些方法都有一个单数版本和一个复数版本。
如GetEvent()对应有一个复数版本GetEvents(),该方法返回一个相关的EventInfo数组
(3)FindMembers()根据查询条件返回一个MemberInfo类型的数组
(4)GetType()该静态方法根据一个字符串名称返回一个Type实例
(5)InvokeMember()对给定项目进行晚期绑定
3.得到一个Type类型实例的三种方法(因为Type是一个抽象类,所以不能直接使用new关键字创建一个Type对象)
(1)使用System.Object.GetType()
e.g:
Personpe=newPerson();//---------定义pe为person类的一个对象
Typet=pe.GetType();
(2)使用System.Type.GetType()静态方法,参数为类型的完全限定名
e.g:
Typet=Type.GetType("Entity.Person");
该方法被重载,允许指定两个布尔类型的参数,一个用来控制当前类型不能找到时是否抛出异常,
另一个用来指示是否区分字符串大小写
Typet=Type.GetType("Entity.Person",false,true);
注意到传入的字符串并没有包含类型所在的程序集信息,此时该类型便被认为是定义在当前执行的程序集中的。
要得到一个外部私有程序集的类型元数据时,字符串参数必须使用类型完全限定名加上类型所在程序集的友好名字
e.g:
Typet=Type.GetType("Entity.Person","Entity");//------"Entity"即为类型所在程序集的友好名字
嵌套类型:
传入的字符串可以指定一个+标记来表示一个嵌套类型,如希望得到一个嵌套在person类中的枚举类型City的类型信息,
则可以这样e.g:
Typet=Type.GetType("Entity.person+City");
(3)使用typeof运算符e.g:
Typet=typeof(person);
三种方法的比较:
使用第一种方法必须先建立一个实例,而后两种方法不必先建立实例。
但使用typeof运算符仍然需要知道类型的编译时信息,而使用System.Type.GetType()静态方法
不需要知道类型的编译时信息,所以是首选方法。
下面是一个实例,简单的运用了前面介绍的知识,实现了对一个Type对象的反射,包括反射其所有可见字段、方法、属性、事件。
反射类型的基本属性。
并将其中一个方法的详细信息列了出来
【源代码】
1usingSystem;
2usingSystem.Collections.Generic;
3usingSystem.Linq;
4usingSystem.Text;
5usingSystem.Reflection;
6
7usingSystem.Collections;//--------要实现IEnumerable接口则必须制定该命名空间
10namespaceExercise
11{
12publicclassBasePerson//-------------假设一个基类,定义了一个公共方法和一个私有方法
13{
14publicvoidBasePublic()
15{
16}
18privatevoidBasePrivate()
19{
20}
21
22};
23
24
25//Person类实现了接口IEnumerable,使得类中定义的Array数组能够使用foreach枚举
26publicclassPerson:
BasePerson,IEnumerable
27{
28privatestringname="林田惠";//---姓名
29publicintage=20;//---年龄
30
31Arraychildren=null;//---子女数组
32
33Person()
34{
35}
37Person(stringa,intb)
38{
39Name=a;
40Age=b;
41}
44publicstringName
45{
46get{returnname;}
47set{}
48}
50publicintAge
51{
52get{returnage;}
53set{}
54}
56publicvoidAddAge()//---自增一岁的方法
57{
58Age+=1;
59}
62publicdelegatevoidPersonNameHandler(stringx);
63publiceventPersonNameHandlerOnChangeName;//------定义了一个改变姓名的事件
65publicvoidChangeName(stringnam)//---改名的方法
66{
67Name=nam;
68}
70publicvoidChangeNameAndAddAge(stringname,intage)//具有两个参数的方法,用来演示反射具体方法的详细情况
71{
72this.Name=name;
73this.Age+=age;
74}
76publicIEnumeratorGetEnumerator()//---实现接口
77{
78returnchildren.GetEnumerator();
79}
81}
85publicclassProgram
86{
89构建自定义元数据查看器#region构建自定义元数据查看器
91//-------------显示传入类型拥有的方法名称
92publicstaticvoidListMethods(Typet)
93{
94Console.WriteLine("\n该类型的所有方法:
");
95MethodInfo[]mi=t.GetMethods();
96foreach(MethodInfominmi)
97{
98Console.WriteLine("\t方法名:
{0}",m.Name);
99}
100}
103//-------------显示一个方法的所有参数信息
104publicstaticvoidListParameters(MethodInfom)
105{
106ParameterInfo[]pi=m.GetParameters();
107foreach(ParameterInfopinpi)
108{
109Console.WriteLine("参数名:
{0}\t参数类型:
{1}",p.Name,p.ParameterType);
110}
111}
114//-------------显示一个特定方法的详细情况
115publicstaticvoidListMethodDetail(Typet,stringMethodName)
116{
117MethodInfom=t.GetMethod(MethodName);
118Console.WriteLine("\n显示方法详情\n方法名称:
{0}",MethodName);
119Console.WriteLine("方法返回值类型:
{0}",m.ReturnType.FullName);
120ListParameters(m);
122}
124//------------显示传入类型拥有的字段名称
125publicstaticvoidListFields(Typet)
126{
127Console.WriteLine("\n该类型的所有字段:
");
128FieldInfo[]fi=t.GetFields();
129foreach(FieldInfofinfi)
130{
131Console.WriteLine("\t字段名:
{0}",f.Name);
132}
133}
135//------------显示传入类型拥有的属性名称
136publicstaticvoidListProperties(Typet)
137{
138Console.WriteLine("\n该类型的所有属性:
");
139PropertyInfo[]pi=t.GetProperties();
140foreach(PropertyInfopinpi)
141{
142Console.WriteLine("\t属性名:
{0}",p.Name);
143}
144}
147//------------显示传入类型拥有的基类名称
148publicstaticvoidListInterFaces(Typet)
149{
150Console.WriteLine("\n该类型的所实现的接口:
");
151Type[]ii=t.GetInterfaces();//-----------对GetInterfaces()的调用返回一个Type类型的数组(说明其实接口也是一种类型)
152foreach(Typepinii)
153{
154Console.WriteLine("\t接口名:
{0}",p.Name);
155}
156}
157
158//------------显示传入类型拥有的属性名称
159publicstaticvoidListEvents(Typet)
160{
161Console.WriteLine("\n该类型的所有事件名:
");
162EventInfo[]ei=t.GetEvents();
163foreach(EventInfoeinei)
164{
165Console.WriteLine("\n事件名:
{0}",e.Name);
166}
167}
169//------------各种其他信息
170publicstaticvoidListOtherInfo(Typet)
171{
172Console.WriteLine("基类名称:
{0}",t.BaseType);
173Console.WriteLine("基类的基类的名称:
{0}",t.BaseType.BaseType);
174Console.WriteLine("是一个类吗?
:
{0}",t.IsClass);
175Console.WriteLine("是一个抽象类吗?
:
{0}",t.IsAbstract);
176}
179#endregion
181publicstaticvoidMain(string[]args)
182{
184Console.WriteLine("----------------------------------------------------------------");
186Typet=Type.GetType("Exercise.Person");//-------Person类的完全限定名为"Exercise.Person"
188ListOtherInfo(t);//反射其他一些信息
189ListFields(t);//反射字段
190ListProperties(t);//反射属性
191ListInterFaces(t);//反射接口
192ListEvents(t);//反射事件
193ListMethodDetail(t,"ChangeNameAndAddAge");//反射一个特定方法的详细信息
194ListMethods(t);//反射方法
197Console.ReadLine();
198}
199}
200}
【实现效果】
【总结】结合源代码和运行效果,总结如下
1.Name属性在编译后成为了get_Name()和set_Name()两个独立的方法
2.OnChangeName事件的注册(+=)和取消注册(-=)分别成为了add_OnChangeName()和remove_OnChangeName方法
3.私有(private)字段name没有被打印出来
4.基类的基类System.Object的成员GetType()和Equals()也被打印了出来,基类的共有方法也被打印出来
为了更好的控制显示我们所想要的信息,下面简单介绍一下FindMembers()方法。
MemberInfo[]mi=t.FindMembers(//【FindMembers】
MemberTypes.Method,//【说明查找的成员类型为Method】
BindingFlags.Public|
BindingFlags.Static|
BindingFlags.NonPublic|//【位屏蔽】
BindingFlags.Instance|
BindingFlags.DeclaredOnly,
Type.FilterName,//【执行比较的委托】
);
将上例的ListMethods方法改为
publicstaticvoidListMethods(Typet)
{
Console.WriteLine(""n该类型的所有方法:
");
MemberInfo[]mi=t.FindMembers(//【FindMembers】
MemberTypes.Method,//【说明查找的成员类型为Method】
BindingFlags.Public|
BindingFlags.Static|
BindingFlags.NonPublic|//【位屏蔽】
BindingFlags.Instance|
BindingFlags.DeclaredOnly,
Type.FilterName,//【执行比较的委托】
"*"
);
foreach(MethodInfominmi)
{
Console.WriteLine(""t方法名:
{0}",m.Name);
}
}
Type.FilterName返回一个MemberFilter类型的委托,它说明按照方法名称进行过滤,最后一个参数“*”,说明返回所有名称(如果使用“Get*”,则会返回所有以Get开头的方法)
现在的输出如下:
可以看到,所有继承而来的方法都没有显示。
。
通过以上实例可以看到System.Reflection命名空间和System.Type类允许我们反射Type实例的大量信息。
然而,对于当前创建的这个实例有一个很大的限制——仅仅能访问当前的程序集,所以接下来要讨论:
如何能使应用程序加载并反射在运行时并不知道的程序集?
1.动态加载程序集
在很多时候我们需要在运行时以编程的方式动态载入程序集,即使那些程序集没有记录在程序清单中。
按需加载外部程序集的操作被称为动态加载
System.Reflection提供了一个名为Assembly的类,使用它我们可以:
(1)动态加载程序集并找到关于程序集自身的属性
(2)动态加载私有或共享程序集
(3)加载任意位置的程序集
从本质上说,Assembly类提供的方法(尤其是Load()和LoadFrom())使得我们可以用编程的方
式提供和客户端.config文件中同样的信息
下面通过一个实例来演示如何通过一个程序集的友好名称来动态加载一个程序集,并打印出其所
包含的每个类、接口、委托等等信息。
【源代码】
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Reflection;
namespaceActiveLoad
{
classProgram
{
//------------定义用来打印程序集中详细情况的函数
staticvoidListAllTypes(Assemblyasm)
{
Console.WriteLine("程序集名称:
{0}",asm.FullName);
Type[]types=asm.GetTypes();
foreach(Typetintypes)
{
Console.WriteLine("\n类型:
{0}\t名称:
{1}",t,t.Name);
}
}
staticvoidMain(string[]args)
{
Assemblyasm=Assembly.Load("Exercise");//--------根据程序集的友好名称动态加载程序集,ActiveLoad项目必须添加对Exercise项目的引用
//ListAllTypes(asm);
asm=Assembly.Load("LINDERMAN.Entity");
ListAllTypes(asm);
Console.ReadLine();
}
}
}
【运行效果】
注意:
1.ActiveLoad项目必须添加对Exercise项目的引用
2.静态方法Assembly.Load方法仅仅传入了一个要加载到内存的程序集的友好名称,因此如果想
反射Exercise.dll,则需要把Exercise.dll文件复制到ActiveLoad应用程序的"Bin"Debug目录,
然后再来运行这个程序。
【此时仍需先在项目中手动添加对Exercise.dll的引用:
添加引用->浏览->ActiveLoad项目的bin文件夹..添加】
3.如果希望让ActiveLoad更加灵活,可以使用Assembly.LoadFrom方法。
此时只要在想查看的程序
集前面加上一个绝对路径。
2.晚期绑定
晚期绑定是一种创建一个给定类型的实例并在运行时调用其成员而不需要在编译时知道它存在的一种技术,对于程序的可扩展性来说非常重要。
要介绍晚期绑定技术,首先必须介绍一下System.Activator类。
System.Activator类除了继承自Object的方法外,其本身只定义了几个成员方法,而且其中大多数都与.net远程处理有关
为了建立一个晚期绑定类型的实例,当前我们只需关注Activator.CreateInstance()方法。
CreateInstance()方法经历过多次重载,其中最简单的变化是带有一个有效的Type对象,描述希望动态分配的实体。
该方法将返回一个基本的Object类型而不是一个强类型——并不是我们所传入的类型。
在Exercise.person类内加入打印个人信息的函数
publicvoidDisplayInfo()
{
Console.WriteLine("姓名:
{0},年龄:
{1}",this.Name,this.Age);
}
下面新建一个LataBinding项目,通过晚期绑定来建立一个Exercise.person类的实例,并调用DisplayInfo()
【源代码】
classProgram
{
staticvoidMain(string[]args)
{
Assemblyasm=Assembly.Load("Exercise");
Typeman=asm.GetType("Exercise.person");
objectMan=Activator.CreateInstance(man);//-------返回一个object类型而不是Exercise.person类型
}
}
得到的实例Man是一个Object类型而不是一个Exercise.person类型。
且不能通过显示转换来解决问题,因为程序并不知道Exercise.person是什么
注意:
晚期绑定的重点是建立编译时未知的对象的实例
下面通过反射来实现打印属性的工作:
首先,使用Type.GetMethod方法
修改源代码如下:
classProgram
{
staticvoidMain(string[]args)
{
Assemblyasm=Assembly.Load("Exercise");
Typeman=asm.GetType("Exercise.Person");//--------注意大小写
objectMan=Activator.CreateInstance(man);//-------返回一个object类型而不是Exercise.person类型
MethodInfomi=man.GetMethod("DisplayInfo");
mi.Invoke(Man,null);
Console.ReadLine(