You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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)}' 暂时不支持,请先在静态构造函数中注册对应的获取逻辑");
    }
}

实现思路拆解

  1. 委托字典缓存:静态构造函数里提前把每种类型对应的xxxValue访问逻辑注册成委托,存到字典里。这样每次调用Get<T>时,直接从字典里取委托执行,比每次用switch(typeof(T))判断类型性能更好,尤其是频繁调用的场景。
  2. 泛型方法的类型安全:通过泛型参数T直接返回对应类型的值,不需要像用Type参数那样做强制类型转换,代码更简洁安全。
  3. 异常处理:提前判断属性是否存在、类型是否支持,抛出明确的异常信息,方便调试。

使用方式

对比原来的写法,现在的代码清爽很多:

// 原来的写法
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

火山引擎 最新活动