C# 反射(Reflection)
概念:
反射指程序可以访问、检测和修改它本身状态或行为的一种能力。
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。
我的理解是,反射的核心就是使用各种类型(Type)相关的 API。
public class MyClass
{
}
public class Test04 : MonoBehaviour
{
void Start()
{
var type = typeof(MyClass);
//程序集
Debug.Log(type.Assembly);
//模块
Debug.Log(type.Module);
//名称
Debug.Log(type.Name);
}
}
优点:
- 1、反射提高了程序的灵活性和扩展性。
- 2、降低耦合性,提高自适应能力。
- 3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
- 1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
- 2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
Type 对象
上面说道反射其实就是利用一些type的API那就介绍一下我们通过API可以实现那些操作。
namespace Q {
public class MyClass
{
}
}
public class Test04 : MonoBehaviour
{
void Start()
{
var type = new MyClass().GetType();
//命名空间+名字
Debug.LogFormat("FullName:{0}", type.FullName);
//是不是类
Debug.LogFormat("IsClass:{0}", type.IsClass);
//命名空间
Debug.LogFormat("Namespace:{0}", type.Namespace);
// 是否是抽象的(抽象类、接口)
Debug.LogFormat("IsAbstract:{0}", type.IsAbstract);
// 是否是值类型
Debug.LogFormat("IsValueType:{0}", type.IsValueType);
}
}
这些都是一些基本的操作,当然type的API不止这些。
namespace Q {
public class MyClass
{
public void A()
{
}
public void B()
{
}
public string str;
public int num;
public int a;
public string c { get; set; }
}
}
public class Test04 : MonoBehaviour
{
void Start()
{
var type = new MyClass().GetType();
//获取当前 Type 直接从中继承的类型。
Debug.LogFormat("BaseType:{0}", type.BaseType);
//返回为当前 Type 的所有公共成员。
Debug.LogFormat("Methods Length:{0}", type.GetMethods().Length);
foreach (var memberInfo in type.GetMembers())
{
Debug.LogFormat("Member Name:{0}", memberInfo.Name);
}
//返回当前 Type 的所有公共字段数量。
Debug.LogFormat("Fields Length:{0}", type.GetFields().Length);
//返回为当前 Type 的所有公共属性数量。
Debug.LogFormat("Properties Length:{0}", type.GetProperties().Length);
//返回为当前 Type 的所有公共成员数量。
Debug.LogFormat("Members Length:{0}", type.GetMembers().Length);
}
}
但是我们发现Methods 的数量 和 Members 的数量和我们声明的成员的数量是不一致的。
public class MyClass
{
public void A()
{
}
public void B()
{
}
public void C()
{
}
}
}
public class Test04 : MonoBehaviour
{
void Start()
{
var type = typeof (MyClass);
Debug.LogFormat("Methods Count:{0}", type.GetMethods().Length);
foreach (var methodInfo in type.GetMethods())
{
Debug.LogFormat("Method Name:{0}", methodInfo.Name);
}
}
}
public 权限的属性器 也被当做两个 Method 了。 GetMethods 除了获取了类中的方法,也获取了父类中的方法,比如 Equals、GetHashCode、GetType、ToString。
GetMethods是没有办法直接获取到私有的成员的但是我们传入相应的flag就能获取到。
namespace Q {
public class MyClass
{
public void A()
{
}
private void B()
{
}
public void C()
{
}
}
}
public class Test04 : MonoBehaviour
{
void Start()
{
// 通过对象的 GetType 获取对象
var type = typeof (MyClass);
// 获取私有对象
// NonPublic 包含非 public 权限的方法
// Public 包含 public 权限的方法
// DeclaredOnly 不包含父类的方法
// Instance 指定实例成员将包括在搜索中。
var resultMethods = type.GetMethods(
BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.DeclaredOnly |
BindingFlags.Instance);
foreach (var methodInfo in resultMethods)
{
Debug.LogFormat("Method Name:{0}", methodInfo.Name);
}
}
}
Members同理我们可以传入相应的flag来限制父类成员的传入。
namespace Q {
public class MyClass
{
public void A()
{
}
private void B()
{
}
public void C()
{
}
}
public class Test04 : MonoBehaviour
{
void Start()
{
// 通过对象的 GetType 获取对象
var type = typeof(MyClass);
var members = type.GetMembers(
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.DeclaredOnly);
foreach (var metInfo in members)
{
Debug.LogFormat("Members Name:{0}", metInfo.Name);
}
}
}
}
Type 获取方式 : typeof , object.GetType
Type 对象的 API : 类信息查询 API , 检测 API ,类结构查询 API , BindingFlags
这就是一个简单的反射体系。
MethodInfo 对象
MethodInfo 顾名思义,就是方法信息的意思。
我们可以从两个地方获取到 MethodInfo 对象。
一个是通过 type.GetMethods() 获取到一个 MethodInfo 数组。
另一个是通过 type.GetMethod(name) 获取一个 MethodInfo 对象。
namespace Q {
public class MyClass
{
public void A()
{
}
private void B()
{
}
public void C()
{
}
}
public class Test04 : MonoBehaviour
{
void Start()
{
// 通过对象的 GetType 获取对象
var type = typeof(MyClass);
var methodAInfo = type.GetMethod("A");
Debug.Log("方法信息查询 API");
Debug.LogFormat("方法名:{0}", methodAInfo.Name);
Debug.Log("方法检测 API");
Debug.LogFormat("是否是泛型方法:{0}", methodAInfo.IsGenericMethod);
Debug.LogFormat("是否是抽象方法:{0}", methodAInfo.IsAbstract);
Debug.LogFormat("是否是构造方法:{0}", methodAInfo.IsConstructor);
Debug.LogFormat("是否是虚方法:{0}", methodAInfo.IsVirtual);
Debug.LogFormat("是否是 public 方法:{0}", methodAInfo.IsPublic);
Debug.LogFormat("是否是 static 方法:{0}", methodAInfo.IsStatic);
Debug.Log("方法结构查询 API");
Debug.LogFormat("Method BaseType:{0}", methodAInfo.GetBaseDefinition());
Debug.LogFormat("方法返回类型:{0}", methodAInfo.ReturnType);
Debug.LogFormat("参数数量:{0}", methodAInfo.GetParameters().Length);
Debug.LogFormat("获取方法体对象:{0}", methodAInfo.GetMethodBody());
}
}
}
通过反射调用对象的方法
MethodInfo 与 Type 不同的是,MethodInfo 多了一个调用方法的 API 类型。
而这个 Invoke 方法,让我们知道了,我们是可以通过反射调用一个对象的方法的。
namespace Q {
public class MyClass
{
public void A()
{
Debug.Log("hello world");
}
private void B()
{
}
public void C()
{
}
}
public class Test04 : MonoBehaviour
{
void Start()
{
// 通过对象的 GetType 获取对象
var someObj = new MyClass();
var type = someObj.GetType();
// 获取 SayHello 的 MethodInfo
var sayHelloMethodInfo = type.GetMethod("A");
// 调用 someObj 的 SayHello 方法
// null 是指没有参数的意思
sayHelloMethodInfo.Invoke(someObj, null);
}
}
}
FieldInfo 对象
FIeldInfo 描述的是字段,字段是可以获取值(GetValue)和设置值的(SetValue),所以 FieldInfo 可以对一个对象设置值或获取值。
namespace Q {
public class MyClass
{
public string str;
public void A()
{
Debug.Log("hello world");
}
private void B()
{
}
public void C()
{
}
}
public class Test04 : MonoBehaviour
{
void Start()
{
// 通过对象的 GetType 获取对象
var someObj = new MyClass();
var type = someObj.GetType();
var fieldAInfo = type.GetField("str");
Debug.LogFormat("(信息)str Name:{0}", fieldAInfo.Name);
Debug.LogFormat("(检测)是否是公开的 ?:{0}", fieldAInfo.IsPublic);
Debug.LogFormat("(检测)是否是 Readonly 的 ?:{0}", fieldAInfo.IsInitOnly);
Debug.LogFormat("(结构)Field 类型:{0}", fieldAInfo.FieldType);
// 设置值
fieldAInfo.SetValue(someObj, "小A");
// 获取值
var fieldAValue = fieldAInfo.GetValue(someObj);
Debug.Log(fieldAValue);
Debug.Log(someObj.str);
}
}
}
代码中的 Assembly 获取方式
准备一个类,把这个类编译成了 dll 文件,并放在磁盘上的某个位置。
using UnityEngine;
namespace q
{
public class HelloWorld
{
public void Say()
{
Debug.Log("Hello World");
}
}
}
接着,我们来通过加载这个 dll 文件,并通过反射来调用它。
void Start()
{
// 通过 dll 的完整路径加载 dll
var assembly = Assembly.LoadFile(Application.streamingAssetsPath + "/ConsoleApp1.dll");
// 获取 SayHelloWorld 类
var type = assembly.GetType("q.HelloWorld");
// 创建对象
var sayHelloWorldObj = Activator.CreateInstance(type);
// 获取 Say 方法
var say = type.GetMethod("Say");
// 调用 sayHelloWorldObj 的 Say 方法
say.Invoke(sayHelloWorldObj, null);
}