You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中实现类Unity的Smart Reference/Handles以实现对象即时销毁?

纯C#实现GameObject包装类的直接成员访问(解决销毁后GC回收问题)

你要解决的核心问题是:让包装类(Smart Reference)能像直接操作真实对象一样调用属性/方法,同时销毁时切断引用让GC能回收真实对象。纯C#里有两种靠谱的方案,不用手动写所有成员也不用依赖C++:

方案1:隐式转换运算符 + 销毁状态检查

这是最省心的实现方式,给包装类加个隐式转换到真实对象类型的运算符,编译器会自动帮你把包装类转成真实对象,这样就能直接写wrapper.transform而不是wrapper.obj.transform。

示例代码:

// 真实的GameObject逻辑类
public class GameObjectImpl
{
    public Transform Transform { get; set; }
    public void Update() { /* 实际更新逻辑 */ }
}

// 包装类(Smart Reference)
public class GameObject
{
    private GameObjectImpl _realObj;
    public bool IsDestroyed { get; private set; }

    public GameObject(GameObjectImpl realObj)
    {
        _realObj = realObj;
        IsDestroyed = false;
    }

    // 隐式转换:让GameObject自动转成GameObjectImpl
    public static implicit operator GameObjectImpl(GameObject wrapper)
    {
        if (wrapper.IsDestroyed)
            throw new ObjectDisposedException(nameof(GameObject), "对象已销毁");
        return wrapper._realObj;
    }

    // 销毁方法:切断对真实对象的引用
    public void Destroy()
    {
        _realObj = null;
        IsDestroyed = true;
    }
}

用的时候直接调用成员就行:

var realObj = new GameObjectImpl { Transform = new Transform() };
var gameObj = new GameObject(realObj);

// 直接访问属性,编译器自动转换
var transform = gameObj.Transform;
// 直接调用方法
gameObj.Update();

// 销毁后再访问会抛出异常(也可以改成返回null,看你需求)
gameObj.Destroy();
// 这行会抛出异常,防止访问已销毁的对象
var invalidTransform = gameObj.Transform;

这个方案优点是维护成本极低,真实对象加新成员不用改包装类;缺点是销毁后访问成员会抛异常(你可以调整逻辑返回null,但要注意空引用风险)。

方案2:动态代理(自定义成员访问拦截)

如果需要更灵活的控制(比如销毁后访问成员自动返回null、打日志),可以实现IDynamicMetaObjectProvider接口,让包装类变成动态对象,运行时拦截所有成员访问请求,转发给真实对象或者处理销毁状态。

简化示例代码:

public class GameObject : IDynamicMetaObjectProvider
{
    private GameObjectImpl _realObj;
    public bool IsDestroyed { get; private set; }

    public GameObject(GameObjectImpl realObj)
    {
        _realObj = realObj;
        IsDestroyed = false;
    }

    public void Destroy()
    {
        _realObj = null;
        IsDestroyed = true;
    }

    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new GameObjectMetaObject(parameter, this);
    }

    private class GameObjectMetaObject : DynamicMetaObject
    {
        private readonly GameObject _wrapper;

        public GameObjectMetaObject(Expression expression, GameObject wrapper)
            : base(expression, BindingRestrictions.Empty, wrapper)
        {
            _wrapper = wrapper;
        }

        // 拦截属性访问
        public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
        {
            if (_wrapper.IsDestroyed)
            {
                // 销毁后返回null
                var nullExpr = Expression.Constant(null, typeof(object));
                return new DynamicMetaObject(nullExpr, BindingRestrictions.GetTypeRestriction(Expression, typeof(GameObject)));
            }

            // 转发给真实对象的属性
            var targetExpr = Expression.Field(Expression.Constant(_wrapper), "_realObj");
            var memberExpr = Expression.PropertyOrField(targetExpr, binder.Name);
            return new DynamicMetaObject(memberExpr, BindingRestrictions.GetTypeRestriction(Expression, typeof(GameObject)));
        }

        // 拦截方法调用
        public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
        {
            if (_wrapper.IsDestroyed)
            {
                // 销毁后调用方法返回null或者抛异常
                return new DynamicMetaObject(Expression.Constant(null, typeof(object)), BindingRestrictions.GetTypeRestriction(Expression, typeof(GameObject)));
            }

            var targetExpr = Expression.Field(Expression.Constant(_wrapper), "_realObj");
            var method = typeof(GameObjectImpl).GetMethod(binder.Name, args.Select(a => a.RuntimeType).ToArray());
            var invokeExpr = Expression.Call(targetExpr, method, args.Select(a => a.Expression));
            return new DynamicMetaObject(invokeExpr, BindingRestrictions.GetTypeRestriction(Expression, typeof(GameObject)));
        }
    }
}

使用时要把包装类声明为dynamic:

dynamic gameObj = new GameObject(new GameObjectImpl { Transform = new Transform() });
// 直接访问属性
var transform = gameObj.Transform;
// 直接调用方法
gameObj.Update();

gameObj.Destroy();
// 销毁后访问返回null
var nullTransform = gameObj.Transform; // 返回null

这个方案优点是完全自定义成员访问逻辑,不用改真实对象代码;缺点是有一点运行时性能开销(动态解析成员),适合对灵活性要求高的场景。


对你列出的四种方式的分析

  • 手动实现所有成员:维护成本爆炸,真实对象改一点就要同步改包装类,绝对不推荐。
  • 包装类存储真实数据:违背了Smart Reference的核心——包装类只存引用不存数据,会导致内存无法释放,直接排除。
  • 依赖C++实现:Unity本身是C内核,所以它的GameObject是C对象的C#包装,但纯C#引擎完全不需要,上面两种方案足够。
  • 未知的C#实现方法:就是上面说的隐式转换运算符和动态代理,这是纯C#环境下的标准实现方式。

内容的提问来源于stack exchange,提问作者Wolf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 14:35:01