如何在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
相关产品推荐
相关产品推荐

