首页 > 其他分享 >【unity】反射机制

【unity】反射机制

时间:2022-09-30 23:12:35浏览次数:88  
标签:反射 int hp unity 实例 机制 Type 成员

前言

很久之前就听说过反射,但不甚理解,今天看到底层终于领悟,遂记录一下相关内容。

C#反射

什么是反射

借用光学中的反射(Reflection)之名,C#中的反射是从对象外部去获取对象内部的各种信息。

这像极了利用波来探测某样物体内部结构,比如金属容器探伤、B超检测体内状况等。

反射是从对象外部获取对象内部的各种信息,具体做法和用途请看如下例子。

以Unity引擎为例,我们来看Unity引擎是如何利用反射机制的。

Unity引擎的反射机制

为Unity编辑器中的游戏对象挂载脚本并编辑好数据,如下(图片仅作为解释说明的例子,不用跟着操作)。
image

public class ReflectionTest : MonoBehaviour
{
    public int hp = 100;
    public int atk = 10;

    public int GetHit(int dmg)
    {
        this.hp -= dmg;
        Debug.Log("GetHit");
        return 10086;
    }

    public void Healing(int healNum)
    {
        this.hp += healNum;
        Debug.Log("Healing");
    }
}

对象上挂载的脚本及其相关数据会保存至场景文件中,如组件名、组件成员变量、成员函数等。

运行时,引擎会加载场景文件,读取场景文件中的数据,实例化节点和组件。

对于客户端开发者来说,新增脚本几乎不可避免;

而对于Unity引擎底层开发来说,加载场景文件时,需要根据组件名来获取类的类型,并对其实例化,挂载到对应游戏对象上。

如果不使用反射,那么每当客户端程序员新增一个脚本时,引擎底层开发人员必须改动相关代码,大抵如下:

string name = "当前组件名";

if(name == "Reflection")
    gameObject.AddComponent<Reflection>();
else if(name == "xxx")
...

于是引入反射来解决这个问题。

反射是怎么做的?

用一种方式来描述任意的类型。即对于每一个类,都建立起一个统一的规则化的描述,使得任意类及其实例均能用该描述来处理。

如何描述一个类?

  1. 类的实例占有一部分内存,其大小就是该类中所有数据成员的大小。那么描述中可以包含类实例的内存大小。

  2. 类的数据成员,其描述大抵如下。

    {"hp" , type int , 偏移0个字节}

    {"atk" , type int , 偏移4个字节}

  3. 类的成员函数,其描述大抵如下。

    {"GetHit" , type 成员函数(静态函数) , 在代码段的位置...}

    {"Healing" , type 成员函数 , 在代码段的位置...}

如何描述一个类的实例?

​ C#为每个类的实例都创建了描述实例,它为Type类型,在System命名空间下。其结构大抵如下。

class FieldInfo
{
	string filedName;
	int type;
	int filedSize;//该字段内存大小
	int offset;//内存偏移
}
class MethodInfo
{
	string methName;
	int type;//静态/普通
	int offset;//函数代码指令的地址
	
}
class Type
{
	int memSize;//当前类的实例的内存大小
	List<FieldInfo> datas;
	List<MethodInfo> funcs;
}

若要描述上文中的类ReflectionTest,其步骤大抵如下。

Type t = new Type();
t.addFiled("hp" , 100);
t.addFiled("atk" , 10);
t.addMethod("GetHit" , 成员方法 , 地址);
t.addMethod("Healing" , 成员方法 , 地址);

编译完成后,我们可以根据编译后的信息,为每个类生成一个描述,写入.exe中。

这样一来,引擎底层就可以根据类的描述来构建实例,访问成员,调用方法了。

string name = "当前组件名";
Type t = System.Type.GetType(name);//它会返回name对应类的描述,这也是为什么脚本不能重名
gameObject.AddComponent(t);

调用底层OS的API来分配一个xxx大小的内存,作为对象实例的内存。

调用构造函数,将该内存块传递给构造函数,初始化对应数据。

总结

  1. 编译每个类时,会为每个类生成一个Type类型的全局数据,其中存放了描述。

    API System.Type.GetType("类型名") typeof(T) 根据类型名或类型来获取 描述对象实例。

  2. 系统已经定义Type类型:FieldsInfos;MethodInfos。

  3. 通过反射来实例化一个对象。API:Type t -> new relation obj

    Activator.CreateInstance (Type)

  4. 在Type中存放了每个数据成员的偏移和大小,因此可以从对象的内存中读取/设置数据的值。

  5. 在Type中存放了每个成员函数的地址。

    methodInfo = t.getMethod("函数名");

    Object returnObj = methodInfo.Invoke(instance , 参数列表);

反射演示

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

public class ReflectionTest : MonoBehaviour
{
    public int hp = 100;
    public int atk = 10;

    public int GetHit(int dmg)
    {
        this.hp -= dmg;
        Debug.Log("GetHit");
        return 10086;
    }

    public void Healing(int healNum)
    {
        this.hp += healNum;
        Debug.Log("Healing");
    }

    private void Start()
    {
        //获取ReflectionTest的类型描述对象实例
        Type t = Type.GetType("ReflectionTest");

        //利用描述实例化一个对象
        var instance = Activator.CreateInstance(t);

        //利用存放的数据成员描述信息为其赋值
        //instance + 偏移 + 大小
        //FieldInfo[] fields = t.GetFields();

        FieldInfo hp = t.GetField("hp");
        hp.SetValue(instance, 50);

        Debug.Log((instance as ReflectionTest).hp);

        //调用成员函数
        MethodInfo m = t.GetMethod("GetHit");
        System.Object[] funcParas = new System.Object[1];
        funcParas[0] = 10;
        System.Object ret = m.Invoke(instance , funcParas);
        Debug.Log(ret);
    }
}

运行结果如下:

image

出现的问题

当我把数据成员或成员函数设置为私有时,上述代码将无法访问对应数据成员或成员函数。

查阅资料后发现需要使用BindingFlags类型枚举才能访问到私有成员,如下。

//通过反射来调私有的成员
Type type = typeof(ReflectionTest);
//BindingFlags类型枚举,BindingFlags.NonPublic | BindingFlags.Instance 组合才能获取到private私有方法
MethodInfo methodInfo = type.GetMethod("GetHit", BindingFlags.NonPublic | BindingFlags.Instance);

参考资料

C#反射机制 - 知乎 (zhihu.com)

C#中的反射到底用在哪,通俗解释(unity)、反射的概念及用法

一节课搞懂c#反射内部原理

c# 通过反射获取私有方法

标签:反射,int,hp,unity,实例,机制,Type,成员
From: https://www.cnblogs.com/OtusScops/p/16746498.html

相关文章

  • Unity中的程序集定义
    Unity项目中,默认我们会出现Assembly-CSharp程序集,编译成Assembly-CSharp.dll文件,在默认情况下我们创建的代码文件均在此程序集中。当我们为Editor添加代码时,只要代码文件放......
  • 反射机制
    反射机制一、静态&动态语言动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码在运行时可以被引进,已有的函数可以被删除或是其他结构上的变化......
  • 反射-理解Class类并获取Class实例
    反射-理解Class类并获取Class实例一、class类介绍在Object类中定义了以下的方法,此方法将被所有子类继承publicfinalnativeClass<?>getClass();getClass方法返......
  • CDN的缓存与回源机制解析
    CDN(ContentDeliveryNetwork,即内容分发网络)指的是一组分布在各个地区的服务器。这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户距离最近,来满足数据的......
  • 注意力机制【4】-多头注意力机制
    所谓自注意力机制就是通过某种运算来直接计算得到句子在编码过程中每个位置上的注意力权重;然后再以权重和的形式来计算得到整个句子的隐含向量表示。自注意力机制的缺陷......
  • 注意力机制【3】-Self Attention
    自注意力与注意力机制的区别在于,自注意力不依赖于外部信息,其qkv均来自内部,或者说来自输入x,就像我们看到一张狗的照片,尽管照片中有其他物体,但人类能自动聚焦到狗的身......
  • 注意力机制【5】Scaled Dot-Product Attention 和 mask
    ScaledDot-ProductAttention 在实际应用中,经常会用到Attention机制,其中最常用的是ScaledDot-ProductAttention,它是通过计算query和key之间的点积来作为之间的相......
  • 20220930-ArrayList扩容机制源码分析②
    本部分对于使用设置初始容量的方法创建ArrayList集合的方式进行源码分析。代码publicclassArrayListSource{publicstaticvoidmain(String[]args){......
  • MySQL——SQL加锁机制简要分析
    前提:针对于InnoDB引擎行锁讨论 锁机制MySQL的锁机制可以分为:锁模型(lock mode)和锁类型(locktype) 锁模型(lock mode)共享锁&排他锁InnoDB实现了两种类型的......
  • 【Unity】浅尝xlua热更新插件
    前言之前的学习中了解到了一些热更新的知识,本想系统地学习基于xLua的热更新框架,但时间紧迫,遂浅尝辄止。在此记录一下相关知识。什么是热更新从云端下载资源包,这些新资源......