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

如何使用ConditionalWeakTable实现多引用作为键的函数记忆化?

用多个引用作为ConditionalWeakTable的键实现记忆化

因为ConditionalWeakTable仅支持单个引用类型作为键,要实现多输入引用的记忆化,有两种实用方案,推荐优先使用嵌套表的方式。

方案一:嵌套ConditionalWeakTable(推荐)

通过多层嵌套的ConditionalWeakTable,每一层对应一个输入引用,最终将计算结果绑定到最内层的键上。这种方式无需自定义类型,完全利用ConditionalWeakTable的原生特性,且能自动处理垃圾回收:当任意一个输入对象被回收时,对应层级的表条目会被移除,下层的缓存数据也会随之被回收。

代码示例

假设你有三个输入类型ObjA、ObjB、ObjC,以及返回类型Result,记忆化实现如下:

using System.Runtime.CompilerServices;

public class MemoryCache
{
    // 三层嵌套的ConditionalWeakTable,每层对应一个输入参数
    private readonly ConditionalWeakTable<ObjA, ConditionalWeakTable<ObjB, ConditionalWeakTable<ObjC, Result>>> _cache = new();

    public Result GetOrCompute(ObjA a, ObjB b, ObjC c)
    {
        // 获取或创建第二层表(对应第一个参数a)
        var level2Cache = _cache.GetValue(a, _ => new ConditionalWeakTable<ObjB, ConditionalWeakTable<ObjC, Result>>());
        // 获取或创建第三层表(对应第二个参数b)
        var level3Cache = level2Cache.GetValue(b, _ => new ConditionalWeakTable<ObjC, Result>());
        // 获取或计算最终结果(对应第三个参数c)
        return level3Cache.GetValue(c, _ => ComputeResult(a, b, c));
    }

    // 实际计算逻辑的函数
    private Result ComputeResult(ObjA a, ObjB b, ObjC c)
    {
        // 这里替换为你的业务计算代码
        return new Result();
    }
}

// 示例类型
public class ObjA { }
public class ObjB { }
public class ObjC { }
public class Result { }

关键特性

  • 线程安全:ConditionalWeakTable的GetValue方法是原子操作,多线程调用不会出现重复计算的问题。
  • 自动垃圾回收:只要任意一个输入对象被回收,对应层级的缓存条目会被自动清理,不会阻止原对象的回收。
  • 高性能:直接使用原对象作为键,缓存命中率高,无需额外的相等性校验开销。

方案二:自定义组合键类型

如果嵌套表的层级过多(比如超过3个输入参数),可以将多个输入引用包装成一个自定义的组合键类型,作为ConditionalWeakTable的键。需要注意组合键必须基于引用相等性比较,且用弱引用持有输入对象,避免阻止原对象被回收。

代码示例

using System.Runtime.CompilerServices;

public sealed class CombinedKey : IEquatable<CombinedKey>
{
    private readonly WeakReference<ObjA> _aRef;
    private readonly WeakReference<ObjB> _bRef;
    private readonly WeakReference<ObjC> _cRef;
    private readonly int _cachedHashCode;

    public CombinedKey(ObjA a, ObjB b, ObjC c)
    {
        _aRef = new WeakReference<ObjA>(a);
        _bRef = new WeakReference<ObjB>(b);
        _cRef = new WeakReference<ObjC>(c);
        // 用RuntimeHelpers.GetHashCode获取对象的引用哈希,保证哈希值不随对象内容变化
        _cachedHashCode = HashCode.Combine(
            RuntimeHelpers.GetHashCode(a),
            RuntimeHelpers.GetHashCode(b),
            RuntimeHelpers.GetHashCode(c));
    }

    public bool Equals(CombinedKey? other)
    {
        if (other is null) return false;
        if (ReferenceEquals(this, other)) return true;

        // 尝试获取原对象,若任意对象已被回收,则不相等
        if (!_aRef.TryGetTarget(out var a) || !other._aRef.TryGetTarget(out var otherA)) return false;
        if (!_bRef.TryGetTarget(out var b) || !other._bRef.TryGetTarget(out var otherB)) return false;
        if (!_cRef.TryGetTarget(out var c) || !other._cRef.TryGetTarget(out var otherC)) return false;

        // 基于引用相等性判断
        return ReferenceEquals(a, otherA) && ReferenceEquals(b, otherB) && ReferenceEquals(c, otherC);
    }

    public override bool Equals(object? obj) => Equals(obj as CombinedKey);
    public override int GetHashCode() => _cachedHashCode;
}

public class MemoryCache
{
    private readonly ConditionalWeakTable<CombinedKey, Result> _cache = new();

    public Result GetOrCompute(ObjA a, ObjB b, ObjC c)
    {
        var key = new CombinedKey(a, b, c);
        return _cache.GetValue(key, _ => ComputeResult(a, b, c));
    }

    private Result ComputeResult(ObjA a, ObjB b, ObjC c)
    {
        return new Result();
    }
}

注意事项

  • 每次调用都会创建新的CombinedKey实例,需要通过Equals方法匹配缓存中的键,性能略低于嵌套表方案。
  • 必须使用WeakReference持有输入对象,否则组合键会强引用输入对象,导致它们无法被垃圾回收。
  • 哈希码需预先计算并缓存,避免每次调用GetHashCode时尝试获取已回收的对象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 11:50:54