什么是反射
反射总结目录
什么是反射
程序运行时将exe、dll文件加载到内存并执行一些操作的过程,这个过程称为
反射
。
通常所说的反射是实现这个过程所使用的技术手段,.Net中System.Reflection等命名空间提供了反射的实现。
反射的原理
通过对程序集元数据的搜索找到对应的成员类型并使用,以实现验证或动态调用程序集的目的。
一个简单的例子引入反射
下面这简单例子引入反射的使用,这个例子中定义了一个Hello
类并添加一个Say
方法,我将使用反射调用Say
方法。
namespace ReflectionStudy
{
public class Hello
{
public void Say()
{
Console.WriteLine("Hello Reflection!");
}
}
}
//使用反射技术调用Say方法
//1.从当前程序集中查找Hello类
var helloType= Assembly.GetExecutingAssembly().GetType("ReflectionStudy.Hello");
//2.获取Hello类的Say方法
var method = helloType.GetMethod("Say");
//3.创建Hello类的实例
var helloInstance=Activator.CreateInstance(helloType);
//4.执行Say方法
method.Invoke(helloInstance, null);
System.Reflection命名空间
上面的例子虽然简单,但是已足够说明反射的大致流程:
- 首先加载程序集
- 在程序集中查找我们需要的类(发现类型)
- 生成类实例
- 找到我们需要使用的
MethodInfo
,FiledInfo
,PropertyInfo
,EventInfo
等成员类型- 最后就是执行我们要执行的动作
1. 加载程序集
因为程序集是个比较大的概念,而这偏离了这篇文章的主题,请移步我的另一篇文章《程序集》。
2. 发现类型
FCL提供了许多API来获取程序集中的类型,目前常用的API是Assembly中的ExportedTypes、DefinedTypes、GetType等,ExportedTypes属性用来获取公开方法即public类型,DefiedTypes属性用来获取所有类型,GetType方法获取一个指定的类型。
var assembly = Assembly.Load(@"mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
int index=0;
Console.WriteLine("获取程序集:{0} 中的ExportedTypes",assembly);
foreach (var type in assembly.ExportedTypes)
{
Console.WriteLine("{0}. {1}", ++index,type);
}
Console.WriteLine("ExportedTypes类型有{0}个",assembly.ExportedTypes.Count());
index = 0;
Console.WriteLine("获取程序集:{0} 中的DefinedTypes", assembly);
foreach (var type in assembly.DefinedTypes)
{
Console.WriteLine("{0}. {1}", ++index, type);
}
Console.WriteLine("DefinedTypes类型有{0}个", assembly.ExportedTypes.Count());
3. 构造类型实例
在FCL中提供了几个构造类型实例的机制分散在
System.Activator
,System.AppDomain
,System.Reflection.ConstructorInfo
中,如果查看源码的话可以看到内部实现调用的都是Activator.CreateInstance
。
下面的例子演示实例的创建
public class Hello
{
public void Say()
{
Console.WriteLine("Hello Reflectioin!");
}
}
//出于演示的目的下面这句简化了加载程序集的步骤
Type helloType = typeof(Hello);
//创建实例
var hello=Activator.CreateInstance(helloType);
4. 发现类型成员
在FCL中有反射提供了一个类型基类
System.Reflection.MemberInfo
,其派生类如下图所示:
通常使用Type类型来发现成员类型如:Method,Filed,Property,Event等。
.Net 4中可以通过Type.GetTypeInfo扩展方法获取TypeInfo
对象以便获取更多功能,相比Type类型TypeInfo代价更高。
下面通过例子来说明如何发现成员:
成员类型描述:
* 一个事件OnSay
* 两个字段,_name是private,Age是Public
* 一个Name属性
* 三个方法,SayHello是Static public,Say是public,HaHa是private
using System;
namespace ReflectionAssembly
{
public class Hello
{
//事件
public event Action OnSay;
//私有字段
private string _name;
//共有字段
public string Age;
//属性
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
//静态方法
public static void SayHello()
{
Console.WriteLine("SayHello");
}
//实例方法
public void Say()
{
Console.WriteLine("Hello Reflectioin!");
}
//测试事件
public void TestEvent()
{
if (OnSay != null)
{
Console.WriteLine($"OnSay绑定了{OnSay.GetInvocationList().Length}个方法");
//遍历执行绑定的事件
foreach (Action onSay in OnSay.GetInvocationList())
{
onSay();
}
}
}
//私有实例方法
void HaHa()
{
Console.WriteLine("HaHa");
}
}
}
因为MemberInfo是所有成员类型的基类,那么我们先看一下获取所有的成员类型:
static void Main(string[] args)
{
var helloType = Assembly.Load("ReflectionAssembly").GetType("ReflectionAssembly.Hello");
var memberInfos = helloType.GetMembers();
foreach (var memberInfo in memberInfos)
{
Console.WriteLine($"成员类型:{memberInfo.MemberType}\t类型名称:{memberInfo.Name}");
}
Console.WriteLine($"类型{helloType.FullName}共{memberInfos.Count()}个成员");
Console.Read();
}
//运行结果如下:
成员类型:Method 类型名称:add_OnSay
成员类型:Method 类型名称:remove_OnSay
成员类型:Method 类型名称:get_Name
成员类型:Method 类型名称:set_Name
成员类型:Method 类型名称:SayHello
成员类型:Method 类型名称:Say
成员类型:Method 类型名称:ToString
成员类型:Method 类型名称:Equals
成员类型:Method 类型名称:GetHashCode
成员类型:Method 类型名称:GetType
成员类型:Constructor 类型名称:.ctor
成员类型:Property 类型名称:Name
成员类型:Event 类型名称:OnSay
成员类型:Field 类型名称:Age
类型ReflectionAssembly.Hello共14个成员
Why?我们只定义了7个成员,结果却显示14个?
- 如果你了解Event类型那么可以忽略add_OnSay,remove_OnSay。《事件和委托学习总结》
- 如果你了解Property那么可以忽略get_Name,set_Name。
- 如果你了解Object那么可以忽略ToString,Equals,GetHasCode,GetType。(所有的引用类型都继承Object)
- 如果你知道默认构造函数那么可以忽略.ctor。
- 现在剩下的
OnSay
,Age
,Name
,Say
,SayHello
5个类是我们定义的,还有2个_name
,Haha
没有在上面运行结果中显示,我们发现这个成员有个共同点是:他们都是私有成员
。
如何获取私有成员
我们将代码稍作改动看看效果:
var memberInfos = helloType.GetMembers();
//上面这行做如下改动
var memberInfos = helloType.GetMembers(BindingFlags.NonPublic //获取private成员
|BindingFlags.Static //获取static成员
|BindingFlags.Instance //获取实例成员
|BindingFlags.Public //获取public成员
);
//得到如下结果:
成员类型:Method 类型名称:add_OnSay
成员类型:Method 类型名称:remove_OnSay
成员类型:Method 类型名称:get_Name
成员类型:Method 类型名称:set_Name
成员类型:Method 类型名称:SayHello
成员类型:Method 类型名称:Say
成员类型:Method 类型名称:HaHa
成员类型:Method 类型名称:ToString
成员类型:Method 类型名称:Equals
成员类型:Method 类型名称:GetHashCode
成员类型:Method 类型名称:GetType
成员类型:Method 类型名称:Finalize
成员类型:Method 类型名称:MemberwiseClone
成员类型:Constructor 类型名称:.ctor
成员类型:Property 类型名称:Name
成员类型:Event 类型名称:OnSay
成员类型:Field 类型名称:OnSay
成员类型:Field 类型名称:_name
成员类型:Field 类型名称:Age
类型ReflectionAssembly.Hello共19个成员
这里我们之关心我们定义SayHello,Say,HaHa,Name,OnSay,_name,Age的7个成员是否被列出来。
GetMembers,FindMembers内部实现
Type类型同时也提供FindMembers方法来获取成员类型,如果观察其内部实现会发现它们仅仅是一个包装方法,
它们通过包装GetMethods,GetFields,GetProperties,GetEvents,GetConstructors方法的实现来获取所有的成员信息。
以下方法均提供了多个重载方法
发现字段(FildInfo)
helloType.GetFields();
发现属性(PropertyInfo)
helloType.GetProperties();
发现方法(MethodInfo)
helloType.GetMethods();
发现构造器(ConstructorInfo)
helloType.GetConstructors();
发现事件(EventInfo)
helloType.GetEvents();
5. 执行对类的操作
调用字段
字段类型通过GetValue
,SetValue
方法对来操作
//实例化Hello
var hello = Activator.CreateInstance(helloType);
//调用private字段
var _name = helloType.GetField("_name",BindingFlags.NonPublic|BindingFlags.Instance);
_name.SetValue(hello, "guodf");
Console.WriteLine(_name.GetValue(hello));
//调用public字段
var age = helloType.GetField("Age");
age.SetValue(hello, "16");
Console.WriteLine(age.GetValue(hello));
调用属性
属性类型通过get_**
,set_**
方法来操作
//实例化Hello
var hello = Activator.CreateInstance(helloType);
//调用属性
var name = helloType.GetProperty("Name");
name.SetValue(hello, "guodf test");
Console.WriteLine($"_name:{_name.GetValue(hello)}\tName:{name.GetValue(hello)}");
调用方法
方法通过Invoke执行方法
//调用实例方法
var say = helloType.GetMethod("Say");
say.Invoke(hello,null);
//调用private实例方法
var haha = helloType.GetMethod("HaHa", BindingFlags.Instance | BindingFlags.NonPublic);
haha.Invoke(hello, null);
调用事件
//调用事件
var testEvent = helloType.GetMethod("TestEvent");
var onSay = helloType.GetEvent("OnSay");
Action event1 = () => { Console.WriteLine("event1"); };
Action event2 = () => { Console.WriteLine("event2"); };
//绑定2个方法
onSay.AddEventHandler(hello, event1);
onSay.AddEventHandler(hello, event2);
testEvent.Invoke(hello,null);
//移除一个方法
onSay.RemoveEventHandler(hello, event1);
testEvent.Invoke(hello,null);
调用静态方法
var sayHello=hello.GetMethod("SayHello");
sayHello.Invoke(null, null);
隐式反射和显式反射
C#中有隐式转换和显式转换得概念,通常派生类转换为基类型被称为隐私转换,因为可以直接将派生类型赋值给基类型;反之称为显示转换。
那么在反射得使用过程中,我通常使用两种实现方式来使用反射对象:一种基于接口的编程方式,另一种则是完全的字符串查找方式。所以我将基于接口的方式称为显式反射,这种做法的好处是编程期间我们可以直接使用类型的方法;而另一种基于字符串找好的方式我称它为隐式反射,因为在使用过程中无论得到那种成员类型都是通过字符串查找实现的。
反射的优缺点
优点
1. 动态加载,按需加载
2. 解耦
缺点
1. 无编译期类型安全检查
2. 性能低
小结
反射一种技术,这种技术可以帮助我们实现一些看起来很酷的编程设计,但这种技术并不完美,它牺牲了效率换来了灵活性。至于这种牺牲的价值当然是仁者见仁智者见智。