浅谈.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 ...
随机推荐
- Nginx 热部署和日志切割,你学会了吗?
上篇文章,我们已经安装好 Nginx,并且配置好 Nginx 文件后,这个时候我就需要操作 Nginx 的命令行了,这篇文章主要讲解 Nginx 命令行相关知识,并通过日常遇到的热部署.切割日志文件场 ...
- 【网络安全】HTTPS为什么比较安全
目录 HTTP和HTTPS简介 SSL协议 SSL协议的主要功能 SSL协议加密数据的原理 用户和服务器的认证流程 TLS 参考 HTTP和HTTPS简介 1. HTTP协议为什么是不安全的 http ...
- Go语言入门:Hello world
本文是「vangoleo的Go语言学习笔记」系列文章之一. 官网: http://www.vangoleo.com/go/go-hello-world/ 在上一篇文章你好,Go语言中,我们对Go语言的 ...
- Flex 和 Bison 使用方法
背景知识 在学编译原理的时候,同时在做南京大学的编译原理课程实验,这里是链接,整个实验的效果是实现一个完整的 C-- 语法的编译器.C-- 语法是他们老师指定的一种类 C 语言. Flex 和 Bis ...
- 智和网管平台SugarNMS助力网络安全运维等保2.0建设
智和信通智和网管平台SugarNMS结合<信息安全技术 网络安全等级保护基本要求>(GB/T 22239-2019)等国家标准文件以及用户提出的网络安全管理需求进行产品设计,推出“监控+展 ...
- P3976 [TJOI2015]旅游(未完成)
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #inc ...
- 0911作业-if while循环小练习
输入姑娘的年龄后,进行以下判断: 如果姑娘小于18岁,打印"不接受未成年" 如果姑娘大于18岁小于25岁,打印"心动表白" 如果姑娘大于25岁小于45岁,打印& ...
- python Django框架正式准备工作
之前由于不太了解数据库方面的知识,但经过一段时间的web应用的开发学习,成功的用其他框架连接了数据库,并完成了相关操作,数据爬取也初识了,更了解了python这门语言的语法,但路还很长,因此现在才能正 ...
- Servlet——用户登录案例
案例:用户登录 * 用户登录案例需求: 1.编写login.html登录页面 username & password 两个输入框 2.使用Druid数据库连接池技术,操作mysql,day14 ...
- linux redhat系列后缀为el5,el6,el7软件包的区别
- EL6软件包用于在Red Hat 6.x, CentOS 6.x, and CloudLinux 6.x进行安装 - EL5软件包用于在Red Hat 5.x, CentOS 5.x, Cloud ...