1. 反射的定义
反射的官方定义是指程序在运行时可以访问, 检测和修改它本身状态或行为的一种能力. 反射的主要用途就是使给定的程序动态的适应不同的运行情况. 在C#中, 反射最基础的API就是typeof(), 它可以提供封装的程序集, 模块和类型的对象.
using UnityEngine;
namespace Assets
{
public class Reflect : MonoBehaviour
{
public class test { }
void Start()
{
var type = typeof(test);
var assembly = type.Assembly;
var module = type.Module;
}
}
}
以上是最基础的反射代码, type即为封装了test类的Type对象, assembly和module分别为test类所在的程序集和模块, 拿到基本的Type对象后, 我们就可以用相关的API实现更多的用途
2. 使用Type对象
Type类包含在System命名空间内, 反编译后可以看到这个类里包含了一百多个属性, 字段和函数. 熟练地使用这些几乎可以完成运行时所有想要的操作, 这里介绍几个基础, 常用的API.
public class test
{
public int a;
public int b { get; }
public void C() { }
}
var type = typeof(test);
Debug.LogError("Fields:" + type.GetFields().Length);
Debug.LogError("Properties:"+ type.GetProperties().Length);
Debug.LogError("Methods:"+ type.GetMethods().Length);
Debug.LogError("Field:" + type.GetField("a").Name);
Debug.LogError("Property:" + type.GetProperty("b").Name);
Debug.LogError("Method:" + type.GetMethod("C").Name);
先在我们的test类里加一些字段属性和函数, 然后使用Type对象的Get___s接口来获取封装类里全部的字段, 属性和函数, 并且分别打印它们的个数, 之后我们通过名字获取到具体的字段属性和函数并且打印它的名字.
运行后发现有五个Log和我们想的应该是一样, 但是在test类中我们只定义了一个method, 打印的log却获取到了6个. 我们可以接着打印一下这些methods的名字, 看一看到底是哪些.
var type = typeof(test);
var methods = type.GetMethods();
Debug.LogError("Methods:"+ methods.Length);
foreach (var method in methods)
{
Debug.LogError(method.Name);
}
我们发现属性的get方法, 以及父类里继承的几个方法里被GetMethods获取到了. 为了不被这些我们不想要的方法影响, 在获取时我们可以加入BindingFlags, 它是存在System.Reflection命名空间里的一个枚举.
public enum BindingFlags
{
Default = 0,
IgnoreCase = 1,
DeclaredOnly = 2,
Instance = 4,
Static = 8,
Public = 16,
NonPublic = 32,
FlattenHierarchy = 64,
InvokeMethod = 256,
CreateInstance = 512,
GetField = 1024,
SetField = 2048,
GetProperty = 4096,
SetProperty = 8192,
PutDispProperty = 16384,
PutRefDispProperty = 32768,
ExactBinding = 65536,
SuppressChangeType = 131072,
OptionalParamBinding = 262144,
IgnoreReturn = 16777216
}
枚举值得具体含义就不赘述了, 反编后有注释. 于是加入BindingFlags后, 我们可以得到
var methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
到这里可能会想, 既然获取到了test类里的C方法, 我们能否调用它呢, 答案是肯定的. 我们修改一下代码, 运行!
public class test
{
public int a;
public int b { get; }
public int C(int i) { return i * i; }
}
var t = new test();
var type = t.GetType();
var method = type.GetMethod("C");
object[] pa = new object[] {15 };
Debug.LogError(method.Invoke(t, pa));
3. Assembly的使用
之前提到Assembly就是程序集, 它包含了经过C#编译后可以直接在.Net环境中运行的代码, 可以直接加载到项目中, 我们在写代码时候就可以直接获取这个程序集并且使用它提供的API. 如果是要在运行时动态加载, 就需要反射来实现.
几种获取程序集的方法:
var type = typeof(test);
var assembly1 = type.Assembly; //test类的程序集
var assembly2 = Assembly.GetExecutingAssembly(); //正在运行的程序集
var assembly3 = Assembly.LoadFile("assemblyPath"); //从assemblyPath路径加载程序集
其中动态加载还有多种Load方式, 可以根据具体需求查询. 获取到程序集后, 我们就可以通过API来创建实例或者获取类等等.