当前位置: 首页 > news >正文

Unity学习之C#的反射机制

        C# 反射(Reflection)是.NET 框架提供的一种高级特性,它允许程序在运行时获取和操作类型信息(如类、方法、属性等),无需在编译时已知这些类型的具体信息。简单来说,反射让代码能够 "观察" 和 "修改" 自身结构,实现了动态性和灵活性。

1. 元数据与反射

        在编程领域,元数据(Metadata) 和反射(Reflection) 是紧密关联的核心概念,二者共同支撑了 “程序自我检查与动态操作” 的能力,是框架开发、动态配置、序列化等高级功能的技术基石。

        元数据的本质是 “描述数据的数据”(Data about Data)。在编程语境中,它特指描述程序代码结构、类型信息、成员属性的数据—— 例如类的名称、方法的参数列表、字段的类型、注解的内容等。元数据不直接参与业务逻辑计算,而是为程序(或工具)提供 “理解代码结构” 的依据。

(1)元数据存在的形式

(2)反射

反射是基于元数据实现的动态编程技术,允许程序在 “运行时” 而非 “编译时”:

a. 检查自身的代码结构(如类、方法、字段);

b. 动态创建对象实例;

c. 调用任意方法、访问 / 修改任意字段(即使是 private 修饰的成员)。

简单来说,反射让程序具备了 “自我审视” 和 “动态操作自身” 的能力,打破了编译期确定的代码执行逻辑限制。

2. Type类

        在 C# 反射系统中,Type 类是核心组件,它代表了类型的元数据,提供了访问类型信息的入口。通过 Type 类,我们可以在运行时获取关于类型的各种信息并进行动态操作。

(1)Type基本概念

  Type 是一个抽象类,位于 System 命名空间下,它表示类型声明,包括类、接口、枚举、结构、委托等。每种类型在 CLR 中都有一个对应的 Type 对象,这个对象包含了该类型的所有元数据信息。

所有类型(包括值类型、引用类型、泛型类型等)都有一个对应的 Type 实例,且每个类型只有一个 Type 实例

(2)获取Type对象方法

方法1:使用typeof运算符(编译时已知类型)
Type intType = typeof(int);
Type stringType = typeof(string);
 方法2:使用 GetType() 方法(对实例对象)
string str = "hello";
Type strType = str.GetType();
方法3:使用 Type.GetType() 静态方法(通过类型全名)
Type type = Type.GetType("System.String");
方法4:从 TypeInfo 获取(.NET 4.5+)
using System.Reflection;Type type = typeof(string).GetTypeInfo().AsType();
方法5:通过程序集获取Type
public class Student
{
}Type studentType = Assembly.GetExecutingAssembly().GetType("Student");

(3)Type类型核心属性

(4)Type常用的方法

a. 获取成员信息
// 获取所有公共构造函数
ConstructorInfo[] constructors = type.GetConstructors();// 获取所有公共方法
MethodInfo[] methods = type.GetMethods();// 获取所有公共字段
FieldInfo[] fields = type.GetFields();// 获取所有公共属性
PropertyInfo[] properties = type.GetProperties();// 获取所有公共事件
EventInfo[] events = type.GetEvents();// 获取所有公共嵌套类型
Type[] nestedTypes = type.GetNestedTypes();
b. 创建实例
// 创建无参实例
object instance = Activator.CreateInstance(type);// 创建带参数的实例
object instance = Activator.CreateInstance(type, param1, param2);
c. 泛型操作
// 创建无参实例
object instance = Activator.CreateInstance(type);// 创建带参数的实例
object instance = Activator.CreateInstance(type, param1, param2);

(5)Type 类的实际应用示例

如何使用 Type 类获取类型信息并动态创建对象和调用方法:

using System;
using System.Reflection;public class Person
{public string Name { get; set; }public int Age { get; set; }public Person() {}public Person(string name, int age){Name = name;Age = age;}public void SayHello(){Console.WriteLine($"Hello, my name is {Name}");}public int CalculateBirthYear(){return DateTime.Now.Year - Age;}
}class Program
{static void Main(){// 获取 Person 类型的 Type 对象Type personType = typeof(Person);// 显示类型基本信息Console.WriteLine($"类型名称: {personType.Name}");Console.WriteLine($"完全限定名: {personType.FullName}");Console.WriteLine($"命名空间: {personType.Namespace}");Console.WriteLine($"基类: {personType.BaseType.Name}");Console.WriteLine($"是否为类: {personType.IsClass}");// 显示类型的属性Console.WriteLine("\n属性:");foreach (PropertyInfo prop in personType.GetProperties()){Console.WriteLine($"- {prop.Name} ({prop.PropertyType.Name})");}// 显示类型的方法Console.WriteLine("\n方法:");foreach (MethodInfo method in personType.GetMethods()){// 排除从 object 继承的方法if (method.DeclaringType == personType){Console.WriteLine($"- {method.Name}");}}// 动态创建对象Console.WriteLine("\n创建对象:");object person = Activator.CreateInstance(personType, "Alice", 30);Console.WriteLine($"创建的对象: {person}");// 动态调用方法Console.WriteLine("\n调用方法:");MethodInfo sayHelloMethod = personType.GetMethod("SayHello");sayHelloMethod.Invoke(person, null);MethodInfo calculateMethod = personType.GetMethod("CalculateBirthYear");int birthYear = (int)calculateMethod.Invoke(person, null);Console.WriteLine($"出生年份: {birthYear}");// 动态设置属性Console.WriteLine("\n设置属性:");PropertyInfo ageProperty = personType.GetProperty("Age");ageProperty.SetValue(person, 31);Console.WriteLine($"新年龄: {ageProperty.GetValue(person)}");}
}

3. 反射的应用

        在 Unity 开发中,C# 的反射机制是一种强大的工具,尤其在开发框架、编辑器工具和需要高度灵活性的系统时非常有用。它允许程序在运行时动态访问和操作类型信息,这在很多场景下能极大提升开发效率和系统扩展性。

(1)通用对象池系统

        对象池通过缓存频繁创建 / 销毁的对象提高性能。反射机制使对象池无需为每种类型编写特定代码,通过动态创建实例和重置字段实现通用化。

        --- 通过 GetFields() 反射扫描标记了 [ResetField] 的字段,缓存元数据避免重复解析

        --- 使用 FieldInfo.SetValue() 动态重置字段值,无需为每种对象编写重置逻辑

using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;/// <summary>
/// 标记需要在对象回收时重置的字段
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ResetFieldAttribute : Attribute { }/// <summary>
/// 通用对象池核心类
/// 通过反射实现类型无关的对象管理
/// </summary>
public class ObjectPool<T> where T : class
{private readonly Queue<T> _pool = new Queue<T>();private readonly Func<T> _createFunc;private readonly List<FieldInfo> _resetFields; // 缓存需要重置的字段信息public int Count => _pool.Count;public ObjectPool(Func<T> createFunc){_createFunc = createFunc ?? throw new ArgumentNullException(nameof(createFunc));// 反射获取所有标记了ResetFieldAttribute的字段(仅执行一次,提高性能)_resetFields = new List<FieldInfo>();foreach (var field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)){if (field.GetCustomAttribute<ResetFieldAttribute>() != null){_resetFields.Add(field);}}}/// <summary>/// 从对象池获取实例/// </summary>public T Get(){return _pool.Count > 0 ? _pool.Dequeue() : _createFunc();}/// <summary>/// 回收实例到对象池(使用反射重置字段)/// </summary>public void Release(T item){if (item == null) return;// 反射重置标记字段foreach (var field in _resetFields){// 根据字段类型设置默认值object defaultValue = field.FieldType.IsValueType ? Activator.CreateInstance(field.FieldType) : null;field.SetValue(item, defaultValue);}// 如果是Unity对象,处理特殊逻辑if (item is Component component){component.gameObject.SetActive(false);}_pool.Enqueue(item);}
}// 使用示例:创建子弹对象池
public class Bullet : MonoBehaviour
{[ResetField] public float damage;[ResetField] private Vector3 _direction;private Rigidbody _rb;private void Awake(){_rb = GetComponent<Rigidbody>();}public void Fire(Vector3 direction, float damage){this.damage = damage;_direction = direction;gameObject.SetActive(true);_rb.AddForce(direction * 20f, ForceMode.Impulse);}
}public class BulletPoolManager : MonoBehaviour
{public Bullet bulletPrefab;private ObjectPool<Bullet> _bulletPool;private void Start(){// 初始化对象池,指定创建函数_bulletPool = new ObjectPool<Bullet>(() => Instantiate(bulletPrefab));}public void SpawnBullet(Vector3 position, Quaternion rotation, Vector3 direction, float damage){var bullet = _bulletPool.Get();bullet.transform.SetPositionAndRotation(position, rotation);bullet.Fire(direction, damage);// 3秒后自动回收Invoke(nameof(ReleaseBullet), 3f, bullet);}private void ReleaseBullet(Bullet bullet){_bulletPool.Release(bullet);}
}

(2) 存档系统(序列化与反序列化)

        存档系统需要将对象状态持久化。反射机制可自动识别需要保存的字段,无需为每个类编写序列化逻辑,极大提高扩展性。

        ---  通过 GetFields() 反射识别标记 [SaveField] 的字段,包括私有字段

        --- 使用 FieldInfo.GetValue() 动态读取值,SetValue() 恢复值

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;/// <summary>
/// 标记需要保存的字段
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class SaveFieldAttribute : Attribute 
{public string Key { get; } // 可选:自定义存档键名public SaveFieldAttribute(string key = null) => Key = key;
}/// <summary>
/// 存档数据容器(存储反射收集的字段值)
/// </summary>
[Serializable]
public class SaveData
{public Dictionary<string, object> Values = new Dictionary<string, object>();
}/// <summary>
/// 存档管理器(使用反射自动序列化/反序列化)
/// </summary>
public class SaveManager : MonoBehaviour
{private string _savePath;private void Awake(){_savePath = Path.Combine(Application.persistentDataPath, "save.dat");}/// <summary>/// 保存对象状态到存档(通过反射收集字段)/// </summary>public void SaveObject(object target, string saveId){if (target == null) return;var saveData = Load() ?? new SaveData();Type targetType = target.GetType();// 反射获取所有需要保存的字段foreach (var field in targetType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)){var saveAttr = field.GetCustomAttribute<SaveFieldAttribute>();if (saveAttr == null) continue;// 生成唯一键:saveId + 类型名 + 字段名(或自定义键)string key = saveAttr.Key ?? $"{saveId}.{targetType.Name}.{field.Name}";// 获取并存储字段值(只保存可序列化类型)if (IsSerializable(field.FieldType)){saveData.Values[key] = field.GetValue(target);}else{Debug.LogWarning($"字段 {field.Name} 类型不可序列化,跳过保存");}}// 保存到文件SaveToFile(saveData);}/// <summary>/// 从存档恢复对象状态(通过反射设置字段)/// </summary>public void LoadObject(object target, string saveId){if (target == null) return;var saveData = Load();if (saveData == null) return;Type targetType = target.GetType();// 反射设置所有标记了SaveField的字段foreach (var field in targetType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)){var saveAttr = field.GetCustomAttribute<SaveFieldAttribute>();if (saveAttr == null) continue;string key = saveAttr.Key ?? $"{saveId}.{targetType.Name}.{field.Name}";if (saveData.Values.TryGetValue(key, out var value)){// 检查类型匹配后设置值if (field.FieldType.IsInstanceOfType(value) || field.FieldType == value.GetType()){field.SetValue(target, value);}else{Debug.LogWarning($"存档中 {key} 的类型与字段类型不匹配");}}}}// 检查类型是否可序列化private bool IsSerializable(Type type){return type.IsSerializable || type.IsPrimitive || type == typeof(string);}// 从文件加载存档private SaveData Load(){if (!File.Exists(_savePath)) return null;try{using (var stream = new FileStream(_savePath, FileMode.Open)){var formatter = new BinaryFormatter();return formatter.Deserialize(stream) as SaveData;}}catch (Exception e){Debug.LogError($"加载存档失败: {e.Message}");return null;}}// 保存存档到文件private void SaveToFile(SaveData data){try{using (var stream = new FileStream(_savePath, FileMode.Create)){var formatter = new BinaryFormatter();formatter.Serialize(stream, data);}}catch (Exception e){Debug.LogError($"保存存档失败: {e.Message}");}}
}// 使用示例
public class PlayerData : MonoBehaviour
{[SaveField] public string playerName;[SaveField("player_level")] public int level = 1; // 自定义键名[SaveField] private int _coins = 0; // 私有字段也可保存private SaveManager _saveManager;private void Start(){_saveManager = FindObjectOfType<SaveManager>();// 加载存档(使用唯一ID区分不同玩家)_saveManager.LoadObject(this, "player_1");}private void OnDestroy(){// 保存存档_saveManager.SaveObject(this, "player_1");}public void AddCoins(int amount){_coins += amount;}
}

http://www.dtcms.com/a/438371.html

相关文章:

  • Python环境管理工具全景对比:Virtualenv, Pipenv, Poetry 与 Conda
  • 郑州企业如何建网站wordpress微信付费
  • 微信小程序入门学习教程,从入门到精通,微信小程序开发进阶(7)
  • 数据结构和算法篇--带哨兵节点的双链表
  • 6黄页网站建设做网站怎么去工信部缴费
  • 三支一扶面试资料
  • pytorch 52 基于SVD从全量训练模型中提取lora模型
  • Process Monitor 学习笔记(5.7):长时间运行追踪与日志体积控制
  • 深入解析需求变更:从本质认知到实践指南
  • 商城网站建设的步骤网络设计教程
  • Day 30 - 错误、异常与 JSON 数据 - Python学习笔记
  • 吴恩达机器学习笔记(10)—支持向量机
  • 电商网站建设与运行xd网页设计教程
  • 基于websocket的多用户网页五子棋(四)
  • 深入浅出 C++20 协程
  • 想做个小网站怎么做主机壳 安装wordpress
  • 永兴县网站建设专业山东省城乡建设厅官网
  • ip prefix-list(IP前缀列表)概念及题目
  • [工作流节点9] 删除记录节点的风险与使用规范 —— 明道云工作流数据清理实战指南
  • 做网站推广的销售怎么打电话如何做网站发产品销售
  • MongoDB GEO 项目场景 ms-scope 实战
  • 医美三方网站怎么做网站外链建设可以提升网站
  • 在算法比赛中高效处理多行输入
  • MySQL 管理与配置详解:从安装到架构解析
  • 构建工具webpack
  • 深入理解 Rust 的内存模型:变量、值与指针
  • 单位网站备案要等多久湖南住建云网站
  • 浦口区网站建设售后服务有没有做卡商的网站
  • 可达鸭模拟赛1
  • LINUX复习资料(一)