浅谈.NET中的反射
一、概述
1、通过反射可以提供类型信息,从而使得我们开发人员在运行时能够利用这些信息构造和使用对象
2、反射机制允许程序在执行过程中动态地添加各种功能
二、运行时类型标识
1、运行时类型标志(RTTI),可以在程序执行期间判断对象类型。例如使用他能够确切的知道基类引用指向了什么类型对象。
2、运行时类型标识,能预先测试某个强制类型转换操作,能否成功,从而避免无效的强制类型转换异常。
3、在C#中有三个支持RTTI的关键字:is、as、typeof。下面一次介绍他们
is运算符:
通过is运算符,能够判断对象类型是否为特定类型,如果两种类型时相同类型,或者两者之间存在引用,装箱拆箱转换,则表明两种类型时兼容的。代码如下:
static void Main()
{
A a = new A();
B b = new B();
if (a is A)
{
Console.WriteLine("a is an A");
} if (b is A)
{
Console.WriteLine("b is an A because it is derived from");
} if (a is B)
{
Console.WriteLine("This won't display,because a not derived from B");
} if (a is object)
{
Console.WriteLine("a is an object");
}
Console.ReadKey();
}
结果:
as运算符:
在运行期间执行类型转换,并且能够是的类型转换失败不抛出异常,而返回一个null值,其实as也可以看作一个is运算符的简化备选方式,如下:
static void Main()
{
A a = new A();
B b = new B();
if (a is B)
{
b = (B) a;//由于a变量不是B类型,因此这里将a变量转换为B类型时无效的
}
else
{
b = null;
} if (b==null)
{
Console.WriteLine("The cast in b=(B)a is not allowed");
}
//上面使用as运算符,能够把两部分合二为一
b = a as B;//as运算符先检查将之转换类型的有效性,如果有效,则执行强类型转换过程,这些都在这一句话完成
if (b==null)
{
Console.WriteLine("The cast in b=(B)a is not allowed");
}
Console.ReadKey();
}
结果:
typeof运算符:
as、is 能够测试两种类型的兼容性,但大多数情况下,还需要获得某个类型的具体信息。这就用到了typeof,他可以返回与具体类型相关的System.Type对象,通过System.Type对象可以去定此类型的特征。一旦获得给定类型的Type对象,就可以通过使用对象定义的各自属性、字段、方法来获取类型的具体信息。Type类包含了很多成元,在接下来的反射中再详细讨论。下面简单的演示Type对象,调用它的三个属性。
static void Main()
{
Type t = typeof(StringBuilder);
Console.WriteLine(t.FullName);//FullName属性返回类型的全称
if (t.IsClass)
{
Console.WriteLine("is a Class");
} if (t.IsSealed)
{
Console.WriteLine("is Sealed");
}
Console.ReadKey();
}
结果:
三、反射的核心类型:System.Type类
1、许多支持反射的类型都位于System.Reflection命名空间中,他们是.net Reflection API的一部分,所以再使用的反射的程序中一般都是要使用System.Reflection的命名空间。
2、System.Type类包装了类型,因此是整个反射子系统的核心,这个类中包含了很多属性和方法,使用这些属性和方法可以再运行时得到类型的信息。
3、Type类派生于System.Reflection.MemberInfo抽象类
MemberInfo类中的只读属性 |
|
属性 |
描述 |
Type DeclaringType |
获取声明该成员的类或接口的类型 |
MemberTypes MemberType |
获取成员的类型,这个值用于指示该成员是字段、方法、属性、事件、或构造函数 |
Int MetadataToken |
获取与特定元数据相关的值 |
Module Module |
获取一个代表反射类型所在模块(可执行文件)的Module对象 |
String Name |
成员的名称 |
Type ReflectedType |
反射的对象类型 |
请注意:
1、MemberType属性的返回类型为MemberTypes,这是一个枚举,它定义了用于表示不同成元的信息值,这些包括:MemberTypes.Constructor、MemeberTypes.Method、MemberTypes.Event、MemberTypes.Property。因此可以通过检查MemberType属性来确定成元的类型,例如在MenberType属性的值为MemberTypes.Method时,该成员为方法
2、MemberInfo类还包含两个与特性相关的抽象方法:
(1)GetCustomAttributes():获得与主调对象相关的自定义特性列表。
(2)IsDefined():确定是否为主调对象定义了相应的特性。
(3)GetCustomeAttributesData():返回有关自定义特性的信息(特性稍后便会提到)
当然除了MemberInfo类定义的方法和属性外,Type类自己也添加了许多属性和方法:如下表(只列出一些常用的,太多二零,自己可以转定义Type类看一下)
Type类定义的方法 |
|
方法 |
功能 |
ConstructorInfo[] GetConstructors() |
获取指定类型的构造函数列表 |
EventInfo[] GetEvents(); |
获取指定类型的时间列 |
FieldInfo[] GetFields(); |
获取指定类型的字段列 |
Type[] GetGenericArguments(); |
获取与已构造的泛型类型绑定的类型参数列表,如果指定类型的泛型类型定义,则获得类型形参。对于正早构造的类型,该列表就可能同时包含类型实参和类型形参 |
MemberInfo[] GetMembers(); |
获取指定类型的成员列表 |
MethodInfo[] GetMethods(); |
获取指定类型的方法列表 |
PropertyInfo[] GetProperties(); |
获取指定类型的属性列表 |
下面列出Type类型定义的常用只读属性
Type类定义的属性 |
|
属性 |
功能 |
Assembly Assembly |
获取指定类型的程序集 |
TypeAttributes Attributes |
获取制定类型的特性 |
Type BaseType |
获取指定类型的直接基类型 |
String FullName |
获取指定类型的全名 |
bool IsAbstract |
如果指定类型是抽象类型,返回true |
bool IsClass |
如果指定类型是类,返回true |
string Namespace |
获取指定类型的命名空间 |
四、使用反射
上面将的这些,都是为了使用反射做铺垫的。
通过使用Type类定义的方法和属性,我们能够在运行时获得类型的各种具体信息。这是一个非常强大的功能,我们一旦得到类型信息,就可以调用其构造函数、方法、属性,可见,反射是允许使用编译时不可用的代码的。
由于Feflection API非常多,这里不可能完整的介绍他们(这里如果完整的介绍,据说要一本书,厚书)。但是Reflection API是按照一定逻辑设计的,因此,只要知道部分接口的使用方法,就可以举一反三的使用剩余的接口。
这里我列出四种关键的反射技术:
1、获取方法的信息
2、调用方法
3、构造对象
4、从程序集中加载类型
五、获取方法的相关信息
一旦有了Type对象就可以使用GetMethodInfo()方法获取此类型支持的所有方法列表。该方法返回一个MethodInfo对象数组,MethodInfo对象表述了主调类型所支持的方法,它位于System.Reflection命名空间中。MethodInfo类派生于MethodBase抽象类,而MethodBase类继承了MemberInfo类,因此,我们能够使用这三各类定义的属性和方法。例如,使用Name属性的到方法名,这里有两个重要的成员:
1、ReturnType属性:为Type类型的对象,能够提供方法的返回类型信息。
2、GetParameters()方法:返回参数列表,参数信息以数组的形式保存在PatameterInfo对象中。PatameterInfo类定义了大量表述参数信息的属性和方法,这里也累出两个常用的属性:Name(包含参数名称信息的字符串),ParameterType(参数类型的信息)。
下面代码我将使用反射获得类中的所支持的方法,还有方法的信息:
class Program
{
static void Main()
{
//获取描述MyClass类型的Type对象
Type t = typeof(MyClass);
Console.WriteLine($"Analyzing methods in {t.Name}");
//MethodInfo对象在System.Reflection命名空间下
MethodInfo[] mi = t.GetMethods();
foreach (var methodInfo in mi)
{
//返回方法的返回类型
Console.Write(methodInfo.ReturnType.Name);
//返回方法的名称
Console.Write($" {methodInfo.Name} (");
//获取方法阐述列表并保存在ParameterInfo对象组中
ParameterInfo[] pi = methodInfo.GetParameters();
for (int i = ; i < pi.Length; i++)
{
//方法的参数类型名称
Console.Write(pi[i].ParameterType.Name);
//方法的参数名
Console.Write($" {pi[i].Name}");
if (i+<pi.Length)
{
Console.Write(", ");
}
} Console.Write(")");
Console.Write("\r\n");
Console.WriteLine("--------------------------");
}
Console.ReadKey();
}
} class MyClass
{
private int x;
private int y; public MyClass()
{
x = ;
y = ;
} public int Sum()
{
return x + y;
} public bool IsBetween(int i)
{
if (x < i && i < y)
{
return true;
} return false;
} public void Set(int a, int b)
{
x = a;
y = b;
} public void Set(double a, double b)
{
x = (int)a;
y = (int)b;
} public void Show()
{
System.Console.WriteLine($"x:{x},y:{y}");
}
}
输出结果:
注意:这里输出的除了MyClass类定义的所有方法外,也会显示object类定义的共有非静态方法。这是因为C#中的所有类型都继承于Object类。另外,这些信息是在程序运行时动态获得的,并不需要知道MyClass类的定义
GetMethods()方法的另一种形式
这种形式可以指定各种标记,已筛选想要获取的方法,他的通用形式为:MethodInfo[] GetMethods(BindingFlags bindingAttr)
BindingFlags是一个枚举,枚举值有(很多,这里只列出5个常用的吧)
(1)DeclareOnly:仅获取指定类定义的方法,而不获取所继承的方法
(2)Instance:获取实例方法
(3)NonPublic:获取非公有方法
(4)Public:获取共有方法
(5)Static:获取静态方法
GetMethods(BindingFlags bindingAttr)这个方法,参数可以使用 or 把两个或更多标记连接在一起,实际上至少要有Instance(或 Static)与Public(或 NonPublic)标记,否则将不会获取任何方法。下我们就写一个示例来演示一下。
class Program
{
static void Main()
{
//获取描述MyClass类型的Type对象
Type t = typeof(MyClass);
Console.WriteLine($"Analyzing methods in {t.Name}");
//MethodInfo对象在System.Reflection命名空间下
//不获取继承方法,为实例方法,·为公用的
MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly|BindingFlags.Instance|BindingFlags.Public);
foreach (var methodInfo in mi)
{
//返回方法的返回类型
Console.Write(methodInfo.ReturnType.Name);
//返回方法的名称
Console.Write($" {methodInfo.Name} (");
//获取方法阐述列表并保存在ParameterInfo对象组中
ParameterInfo[] pi = methodInfo.GetParameters();
for (int i = ; i < pi.Length; i++)
{
//方法的参数类型名称
Console.Write(pi[i].ParameterType.Name);
//方法的参数名
Console.Write($" {pi[i].Name}");
if (i+<pi.Length)
{
Console.Write(", ");
}
} Console.Write(")");
Console.Write("\r\n");
Console.WriteLine("--------------------------");
}
Console.ReadKey();
}
} class MyClass
{
private int x;
private int y; public MyClass()
{
x = ;
y = ;
} public int Sum()
{
return x + y;
} public bool IsBetween(int i)
{
if (x < i && i < y)
{
return true;
} return false;
} public void Set(int a, int b)
{
x = a;
y = b;
} public void Set(double a, double b)
{
x = (int)a;
y = (int)b;
} public void Show()
{
System.Console.WriteLine($"x:{x},y:{y}");
}
}
输出结果:
上面例子可以看出,只显示了MyClass类显示定义的方法,private int Sum() 也不显示了
六、使用反射调用方法
上面我们通过反射获取到了类中的所有信息,下面我们就再使用反射调用反射获取到的方法。要调用反射获取到的方法,则需要在MethodInfo实例上调用Invoke()方法,Invoke()的使用,在下面例子中演示说明:
下面例子是先通过反射获取到要调用的方法,然后使用Invoke()方法,调用获取到的指定方法:
class Program
{
static void Main()
{
//获取描述MyClass类型的Type对象
Type t = typeof(MyClass);
MyClass reflectObj = new MyClass();
reflectObj.Show();
//不获取继承方法,为实例方法,·为公用的
MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
foreach (var methodInfo in mi)
{ //获取方法阐述列表并保存在ParameterInfo对象组中
ParameterInfo[] pi = methodInfo.GetParameters();
if (methodInfo.Name.Equals("Set", StringComparison.Ordinal) && pi[].ParameterType == typeof(int))
{
object[] args = new object[];
args[] = ;
args[] = ;
methodInfo.Invoke(reflectObj,args);
}
}
Console.ReadKey();
}
} class MyClass
{
private int x;
private int y; public MyClass()
{
x = ;
y = ;
} public int Sum()
{
return x + y;
} public bool IsBetween(int i)
{
if (x < i && i < y)
{
return true;
} return false;
} public void Set(int a, int b)
{
x = a;
y = b;
Show();
} private void Set(double a, double b)
{
x = (int)a;
y = (int)b;
} public void Show()
{
System.Console.WriteLine($"x:{x},y:{y}");
}
}
获取Type对象的构造函数
这个之前的阐述中,由于MyClass类型的对象都是显示创建的,因此使用反射技术调用MyClass类中的方法是没有任何优势的,还不如以普通方式调用方便简单呢,但是,如果对象是在运行时动态创建的,反射功能的优势就会显现出来。在这种情况下,要先获取一个构造函数列表,然后调用列表中的某个构造函数,创建一个该类型的实例,通过这种机制,可以在运行时实例化任意类型的对象,而不必在声明语句中指定类型。
示例代码如下:
class Program
{
static void Main()
{
//获取描述MyClass类型的Type对象
Type t = typeof(MyClass);
int val;
//使用这个方法获取构造函数列表
ConstructorInfo[] ci = t.GetConstructors();
int x;
for (x = ; x < ci.Length; x++)
{
//获取当构造参数列表
ParameterInfo[] pi = ci[x].GetParameters();
if (pi.Length == )
{
//如果当前构造函数有2个参数,则跳出循环
break;
}
} if (x == ci.Length)
{
return;
}
object[] consArgs = new object[];
consArgs[] = ;
consArgs[] = ;
//实例化一个这个构造函数有连个参数的类型对象,如果参数为空,则为null object reflectOb = ci[x].Invoke(consArgs); MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
foreach (var methodInfo in mi)
{
if (methodInfo.Name.Equals("Sum", StringComparison.Ordinal))
{
val = (int)methodInfo.Invoke(reflectOb, null);
Console.WriteLine($"Sum is {val}");
}
}
Console.ReadKey();
}
} class MyClass
{
private int x;
private int y; public MyClass(int i)
{
x = y + i;
} public MyClass(int i, int j)
{
x = i;
y = j;
} public int Sum()
{
return x + y;
}
}
输出结果:
七、从程序集获得类型
在这之前的阐述中可以看出一个类型的所有信息都能够通过反射得到,但是MyClass类型本身,我们却没有做到获取,虽然前面的阐述实例,可以动态确定MyClass类的信息,但是他们都是基于以下事实:预先知道类型名称,并且在typeof与剧中使用它获得Type对象。尽管这种方式可能在很多情况下都管用,但是要发挥反射的全部功能,我们还需要分析反射程序集的内容来动态确定程序的可用类型。
借助Reflection API,可以加载程序集,获取它的相关信息并创建其公共可用类型的实例,通过这种机制,程序能够搜索其环境,利用潜在的功能,而无需再编译期间显示的定义他们,这是一个非常有效且令人兴奋的概念。为了说明如何获取程序集中的类型,我创建了两个文件,第一个文件定义一组类,第二个文件则反射各个类型的信息。代码效果如下:
1、这下面代码编译生成MyTest2_C.exe文件
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello word !");
Console.ReadKey();
}
} class MyClass
{
private int x;
private int y; public MyClass(int i)
{
x = y + i;
} public MyClass(int i, int j)
{
x = i;
y = j;
} public int Sum()
{
return x + y;
}
}
2、这下面的代码时获取上面生成程序集的
class Program
{
static void Main()
{
//加载指定的程序集
Assembly asm = Assembly.LoadFrom(@"E:\自己的\MyTest\MyTest2_C\bin\Debug\MyTest2_C.exe");
//获取程序集中的所有类型列表
Type[] allType = asm.GetTypes();
foreach (var type in allType)
{
//打印出类型名称
Console.WriteLine(type.Name);
} Console.ReadKey();
}
}
输出结果:
上面获取到了程序集中的类型,如果像操作程序集类型中的方法,则跟前面我们表述的方法一样操作即可。
好了,.Net反射我们就介绍到这里啦~
浅谈.NET中的反射的更多相关文章
- 浅谈JS中的!=、== 、!==、===的用法和区别 JS中Null与Undefined的区别 读取XML文件 获取路径的方式 C#中Cookie,Session,Application的用法与区别? c#反射 抽象工厂
浅谈JS中的!=.== .!==.===的用法和区别 var num = 1; var str = '1'; var test = 1; test == num //tr ...
- 浅谈Java中的equals和==(转)
浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str ...
- 浅谈Linux中的信号处理机制(二)
首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号处理机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Lin ...
- 浅谈Java中的对象和引用
浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...
- 浅谈Java中的equals和==
浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: String str1 = new String("hello"); String str2 = ...
- 转【】浅谈sql中的in与not in,exists与not exists的区别_
浅谈sql中的in与not in,exists与not exists的区别 1.in和exists in是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表 ...
- 浅谈iOS中的userAgent
浅谈iOS中的userAgent User-Agent(用户代理)字符串是Web浏览器用于声明自身型号版本并随HTTP请求发送给Web服务器的字符串,在Web服务器上可以获取到该字符串. 在公司产 ...
- 浅谈JavaScript中的闭包
浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...
- 浅谈sql中的in与not in,exists与not exists的区别
转 浅谈sql中的in与not in,exists与not exists的区别 12月12日北京OSC源创会 —— 开源技术的年终盛典 » sql exists in 1.in和exists ...
随机推荐
- Node.js 使用 express-jwt 解析 JWT
Node.js 上 Token 鉴权常用的是 passport,它可以自定义校验策略,但如果你是用 express 框架,又只是解析 JWT 这种简单需求,可以尝试下 express-jwt 这个中间 ...
- firefox浏览器播放音频
之前做的系统,在firefox浏览器下有更好的使用体验.因此要求客户统一使用firefox浏览器,前段时间客户要求在系统中加入音频效果. 在网上查了下,主要用到的标签有<bgsound>, ...
- [Hive]Hive架构及常规操作
Hive架构 如图中所示,Hive通过给用户提供的一系列交互接口,接收到用户的指令(SQL),使用自己的Driver,结合元数据(MetaStore),将这些指令翻译成MapReduce,提交到Had ...
- hadoop2.6集群环境搭建
版权声明:本文为博主原创文章,未经博主允许不得转载. 一.环境说明 1.机器:一台物理机 和一台虚拟机 2.Linux版本:[Spark@S1PA11 ~]$ cat /etc/issueRed Ha ...
- 带你上手一款下载超 10 万次的 IDEA 插件
作者 | 倪超(银时) 阿里云开发者工具产品专家 本文整理自 11 月 7 日社群分享,每月 2 场高质量分享,点击加入社群. 导读:Cloud Toolkit 是本地 IDE 插件,帮助开发者更高效 ...
- ie浏览器兼容性的入门解决方案
IE浏览器的兼容性素来是令人头疼的问题,大名鼎鼎的FUCK-IE不是浪得虚名的. 这里使用的解决方案是HACK,具体原理就是针对不同的浏览器写不同的HTML.CSS样式,从而使各种浏览器达到一致的渲染 ...
- 用python实现你的绘画梦想
导语: 你是否还在为当时年少时没有选择自己的梦想而伤心,是否还在为自己的无法成为绘画名家而苦恼,这一切都不需要担心.python都能帮你实现,诶!python怎么能画画呢,一些简单的图案没问题,但 ...
- day1 晚上 P4145 上帝造题的七分钟2 / 花神游历各国 线段树
#include<iostream> #include<cstdio> #include<cmath> using namespace std; ; struct ...
- 卖饲料——单调队列优化dp
题目描述 约翰开车来到镇上,他要带K吨饲料回家.运送饲料是需要花钱的,如果他的车上有X吨饲料,每公里就要花费X^2元,开车D公里就需要D* X^2元.约翰可以从N家商店购买饲料,所有商店都在一个坐标轴 ...
- Xshell6配置ssh免密码登录虚拟机
首先先说明一下有密码的,涉及到root登陆权限的问题: 1.用超级管理员身份登录,修改 vi /etc/ssh/sshd_config, 找到 把其中的permitRootLogin 修改成: # ...