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运算符(编译时已知类型)
cs 复制代码
Type intType = typeof(int);
Type stringType = typeof(string);
方法2:使用 GetType() 方法(对实例对象)
cs 复制代码
string str = "hello";
Type strType = str.GetType();
方法3:使用 Type.GetType() 静态方法(通过类型全名)
cs 复制代码
Type type = Type.GetType("System.String");
方法4:从 TypeInfo 获取(.NET 4.5+)
cs 复制代码
using System.Reflection;

Type type = typeof(string).GetTypeInfo().AsType();
方法5:通过程序集获取Type
cs 复制代码
public class Student
{
}

Type studentType = Assembly.GetExecutingAssembly().GetType("Student");

(3)Type类型核心属性

(4)Type常用的方法

a. 获取成员信息
cs 复制代码
// 获取所有公共构造函数
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. 创建实例
cs 复制代码
// 创建无参实例
object instance = Activator.CreateInstance(type);

// 创建带参数的实例
object instance = Activator.CreateInstance(type, param1, param2);
c. 泛型操作
cs 复制代码
// 创建无参实例
object instance = Activator.CreateInstance(type);

// 创建带参数的实例
object instance = Activator.CreateInstance(type, param1, param2);

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

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

cs 复制代码
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() 动态重置字段值,无需为每种对象编写重置逻辑

cs 复制代码
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() 恢复值

cs 复制代码
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;
    }
}
    
相关推荐
绿荫阿广3 小时前
用纯.NET开发并制作一个智能桌面机器人(六):使用.NET开发一个跨平台功能完善的小智AI客户端
c#·.net·asp.net core·maui·winui
Brookty5 小时前
深入解析Java类加载与实例化流程
java·开发语言·学习
Broken Arrows5 小时前
k8s学习(一)——kubernetes重要基础概念概述
学习·容器·kubernetes
straw_hat.5 小时前
PCB学习——STM32F103VET6-STM32接口部分
stm32·嵌入式硬件·学习
周杰伦fans5 小时前
c#设计模式—访问者模式
c#·.net
悠哉悠哉愿意6 小时前
【数据结构与算法学习笔记】双指针
数据结构·笔记·python·学习·算法
charlie1145141917 小时前
Windows 10系统编程——进程专题:枚举我们进程的状态
c++·windows·学习·操作系统·进程
sealaugh327 小时前
AI(学习笔记第九课) 使用langchain的MultiQueryRetriever和indexing
人工智能·笔记·学习
bawangtianzun7 小时前
树的重心与直径 性质
数据结构·c++·学习·算法