如何在C#中为SerializedObject实现泛型Get扩展方法替代属性值访问?
这个需求在Unity开发里挺常见的——想简化SerializedObject获取属性值的代码,避免每次都写FindProperty再加对应的xxxValue。既然C#没有模板特化,我们可以用委托字典缓存的方式来实现这个泛型扩展方法,既简洁又保证性能。
下面直接上实现代码,然后我会拆解解释:
using UnityEngine; using System; using System.Collections.Generic; public static class SerializedObjectExtensions { // 用字典缓存每种类型对应的属性值获取逻辑,避免重复类型判断 private static readonly Dictionary<Type, Delegate> _propertyGetters = new Dictionary<Type, Delegate>(); static SerializedObjectExtensions() { // 提前注册Unity常用的SerializedProperty类型对应的获取方法 RegisterGetter<bool>(prop => prop.boolValue); RegisterGetter<int>(prop => prop.intValue); RegisterGetter<float>(prop => prop.floatValue); RegisterGetter<string>(prop => prop.stringValue); RegisterGetter<Color>(prop => prop.colorValue); RegisterGetter<Vector2>(prop => prop.vector2Value); RegisterGetter<Vector3>(prop => prop.vector3Value); RegisterGetter<GameObject>(prop => prop.objectReferenceValue as GameObject); // 你可以根据自己的需求继续添加更多类型,比如Vector4、Quaternion等 } private static void RegisterGetter<T>(Func<SerializedProperty, T> getter) { _propertyGetters[typeof(T)] = getter; } public static T Get<T>(this SerializedObject serializedObject, string propertyPath) { var property = serializedObject.FindProperty(propertyPath); if (property == null) { throw new ArgumentException($"找不到路径为 '{propertyPath}' 的属性"); } if (_propertyGetters.TryGetValue(typeof(T), out var getterDelegate)) { // 把委托转换成对应的泛型Func,然后调用获取值 return ((Func<SerializedProperty, T>)getterDelegate).Invoke(property); } throw new NotSupportedException($"类型 '{typeof(T)}' 暂时不支持,请先在静态构造函数中注册对应的获取逻辑"); } }
实现思路拆解
- 委托字典缓存:静态构造函数里提前把每种类型对应的
xxxValue访问逻辑注册成委托,存到字典里。这样每次调用Get<T>时,直接从字典里取委托执行,比每次用switch(typeof(T))判断类型性能更好,尤其是频繁调用的场景。 - 泛型方法的类型安全:通过泛型参数
T直接返回对应类型的值,不需要像用Type参数那样做强制类型转换,代码更简洁安全。 - 异常处理:提前判断属性是否存在、类型是否支持,抛出明确的异常信息,方便调试。
使用方式
对比原来的写法,现在的代码清爽很多:
// 原来的写法 var oldBoolValue = mySerializedObject.FindProperty("myBoolProperty").boolValue; var oldFloatValue = mySerializedObject.FindProperty("myFloatProperty").floatValue; // 现在的写法 var newBoolValue = mySerializedObject.Get<bool>("myBoolProperty"); var newFloatValue = mySerializedObject.Get<float>("myFloatProperty");
扩展自定义类型
如果需要支持自定义的可序列化类或者其他Unity类型,只需要在静态构造函数里添加一行注册代码就行。比如你的自定义类PlayerData:
// 在静态构造函数里添加 RegisterGetter<PlayerData>(prop => prop.objectReferenceValue as PlayerData);
这种方式完美替代了模板特化的效果,而且完全符合标准C#的语法,不需要依赖任何特殊特性。
内容的提问来源于stack exchange,提问作者Maroš Beťko




