【Unity学习笔记】反射


文章目录


前言

在我平时做项目的时候,由于我们做的项目都是很简单的,所以不怎么接触反射机制。最早了解反射机制是关于Invoke的时候,知道可以通过方法名来直接进行Invoke调用,但是由于反射调用存在性能开销较大的问题,因此就没打算深入了解

不过反射作为C#的高级特性,可以不用,但是不能不了解

反射(Reflection) 的含义和用法

反射

csharp 复制代码
反射是.NET中的重要机制,通过反射可以得到*.exe或*.dll等程序集内部的接口、类、方法、字段、属性、特性等信息,还可以动态创建出类型实例并执行其中的方法。
反射指程序可以访问、检测和修改它本身状态或行为的一种能力。
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。
可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。

简单来说,反射能实现的功能极其强大,可以直接通过读取exe或者dll程序集获取其中的接口、类、方法、字段、属性、特性等信息。

通过反射获取类型

反射获取类型的方式有三种:

  1. 通过typeof获取某个值的类型
csharp 复制代码
Type personType=typeof(Person);

2.通过一个对象获取该对象所对应的类的类型

csharp 复制代码
Type=Person.GetType();

3.通过类的名称字符串获取对应的类型

csharp 复制代码
Type strType =Type.GetType("Person");

注意,上述说的三种方法不止包括获取class,只需要换成对应的方法就能获取接口、方法、字段、属性、特性等等信息。这意味这只要使用反射就可以获取代码中的几乎任何信息。甚至私有的变量成员和方法都能获取

反射(Reflection) 的含义和用法

只需查看上文就可以知道反射的功能有多全面,返回所需的类型的信息,根据访问修饰符获取类型成员信息,通过反射直接构造实例化对象,通过反射获取类中的所有属性,字段,事件,方法,构造函数等等。私有的都可以随便访问。

优点:

  • 反射提高了程序的灵活性和扩展性。
  • 降低耦合性,提高自适应能力。
  • 它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

缺点:

  1. 性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
  2. 使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。

实际上反射的优点也是它的缺点。为什么我们不用反射来解耦,不用反射做拓展呢?除了反射本身调用时需要查找解释造成过高的性能开销之外,反射本身绕过了程序内部逻辑,可读性太差了。如果我们要使用反射调用函数,不还是需要知道函数方法实现了什么吗?而解耦这一目的完全可以从设计模式上来解决。

Unity中的反射

反射在Unity中实现的功能主要是:

  • 使用反射,我们可以动态的访问代码中的成员,或是进行动态实例化。例如我们想要实现游戏中的控制台Debug功能,让用户可以使用简单的指令就能创建一些游戏实例,例如用户可以用指令add ObjName 100来为场景中增加100个对应名称的游戏物体实例,我们就可以用反射机制,获取ObjName字符串对应的Type并生成物体:
csharp 复制代码
Type type = typeof(ObjName);
object instance = Activator.CreateInstance(type);
  • 另一种想法是,使用反射,我们可以实现一些热更新的功能。例如对于若要生成一个物体,我们可以把它封装在dll程序集中,并通过反射机制,用物体的名称来直接实例化dll中的该物体。而如果此时客户端要实现不停机热更新该物体的数值,只需下载替换dll文件即可,因为物体名称并没变,我们通过反射机制获取直接获取更新的成员并更新数值。

合理使用反射机制,可以简单的实现一些麻烦的功能,而且将程序集之间进行分离,也有助于减少程序的耦合性。


用反射在Unity中动态加载

想要在unity中创建并加载程序集,我们需要在文件夹内生成一个Assembly Definition


我们会发现创建了一个拼图icon的文件,这个文件就是我们的程序集,但它目前是未编译的状态,格式是asmdef,只有在被导出后才会被编译为dll

官方文档------程序集定义
Unity程序集定义(Assembly Definition File)功能详解

我们在与它同目录下所创建的脚本都会被编译到这个程序集中

在面板中可以查看它的属性,首先程序集的名称是在面板上的Name定义的,而不是该文件本身的名称

这里显示了三个选项(高版本还有其他选项),AutoReferenced代表了该程序集会自动引用其他程序集,导致其他程序集更新后该程序集也被自动重编译,如果我们不希望这个程序集在其他程序集更新后被重编译,就关闭它

override References代表了我们指定该程序集会引用哪些程序集,并在Assembly Definition References里选择添加对应的Dll

最下面的面板Platforms约定在导出到哪些平台时该程序集会被编译

Define Constraints代表了该程序集会在哪些宏被定义的时候被编译,只有当代码中使用了指定宏时才会使用该程序集。例如我下面的代码:

csharp 复制代码
using System.IO;
using System.Reflection;
using UnityEngine;

public class TestClass : MonoBehaviour
{
	private string _localPath;

	private void Start()
	{

#if UNITY_EDITOR
		// 我不知道如何在项目中直接加载未编译的程序集,只能导出后加载了
		_localPath = Path.Combine(Path.GetDirectoryName(Application.dataPath),"Apps");
		string[] DataFloder = Directory.GetDirectories(_localPath, "*_Data");
		_localPath = Path.Combine(DataFloder[0], "Managed", "Test.dll");
#else
		LocalPath = Path.Combine(Application.dataPath, "Managed", "Test.dll");
#endif
		// 可笑的是程序集只能加载不能卸载,导致程序关闭后程序集依然被访问
		Assembly _assembly = Assembly.LoadFrom(_localPath);
		var t = _assembly.GetType("TestReflect");
		gameObject.AddComponent(t);
	}
}
csharp 复制代码
public class TestReflect : MonoBehaviour
{
    void Start()
    {
        Debug.Log("反射成功调用");
    }
}

由于我定义了!UNITY_EDITOR,也就是非编辑器中被编译,经测试,导出时会正常编译dll,然后在编辑器状态,代码是正常执行的。

但是如果定义的是UNITY_EDITOR,则导出时不会编译为Dll,猜想是由于导出时的bulidPipeline使用了!UNITY_EDITOR宏,因此若定义了!UNITY_EDITOR的引用约束,则导出时会编译。

当然我们还可以定义其他的编译引用约束,根据具体使用情况来判断

导出后的Dll路径在GameScence_Data\Managed\路径下

执行结果:


导出后的结果也是一样的

相关推荐
结衣结衣.7 分钟前
python中的函数介绍
java·c语言·开发语言·前端·笔记·python·学习
LN-ZMOI34 分钟前
c++学习笔记1
c++·笔记·学习
五味香38 分钟前
C++学习,信号处理
android·c语言·开发语言·c++·学习·算法·信号处理
qq_421833671 小时前
计算机网络——应用层
笔记·计算机网络
云端奇趣1 小时前
探索 3 个有趣的 GitHub 学习资源库
经验分享·git·学习·github
我感觉。1 小时前
【信号与系统第五章】13、希尔伯特变换
学习·dsp开发
知识分享小能手2 小时前
mysql学习教程,从入门到精通,SQL 修改表(ALTER TABLE 语句)(29)
大数据·开发语言·数据库·sql·学习·mysql·数据分析
冰榫2 小时前
9.30学习记录(补)
学习
dangoxiba2 小时前
[Unity Demo]从零开始制作空洞骑士Hollow Knight第十三集:制作小骑士的接触地刺复活机制以及完善地图的可交互对象
游戏·unity·visualstudio·c#·游戏引擎
@qike2 小时前
【C++】—— 日期类的实现
c语言·c++·笔记·算法·学习方法