代码之家  ›  专栏  ›  技术社区  ›  Christoph Wolf

是类型。isSubclassOf(Type otherType)缓存,还是我必须自己这样做?

  •  2
  • Christoph Wolf  · 技术社区  · 6 年前

    简单问题:

    是类型。isSubclassOf(键入otherType)缓存为 Dictionary<Tuple<Type, Type>, bool> ?

    如果没有,这样的电话有多贵?

    我经常检查这一点以保持代码的可扩展性,并将我使用最多的方法转换为字典。。。

    3 回复  |  直到 6 年前
        1
  •  4
  •   Titian Cernicova-Dragomir    6 年前

    虽然这并不理想,但取决于实现细节,我们可以看看 IsSubclassOf 使用 dnSpy

    public virtual bool IsSubclassOf(Type c)
    {
        Type type = this;
        if (type == c)
        {
            return false;
        }
        while (type != null)
        {
            if (type == c)
            {
                return true;
            }
            type = type.BaseType;
        }
        return false;
    }
    

    所以简单的回答是,在这个版本的框架中( 4.6 )调用不会被缓存,这意味着向上遍历继承层次结构。

    这个电话有多贵的问题取决于您的用例。您应该衡量代码是否在该方法中花费了大量时间,以及缓存是否有帮助。

    表演

    是否值得缓存结果的问题在于,衡量调用与缓存查找所需的时间。我测试了5种场景:

    1. 直接调用
    2. 缓存使用: Dictionary<Tuple<Type, Type>, bool>
    3. 缓存使用: Dictionary<(Type, Type), bool> (值元组)
    4. 缓存使用: ConcurrentDictionary<Tuple<Type, Type>, bool>
    5. 缓存使用: ConcurrentDictionary<(Type, Type), bool> (值元组)

    后果

    1. 直接调用-0.15s/调用
    2. 缓存使用: 词典(<元组(<);类型,类型(>,布尔值(>); -0.12秒/次
    3. 缓存使用: 词典(<(类型,类型),bool> -0.06秒/次
    4. 缓存使用: ConcurrentDictionary<元组(<);类型,类型(>,布尔值(>); -0.13s/call
    5. 缓存使用: ConcurrentDictionary<(类型,类型),bool> (值元组)-0.7s/call

    ConcurrentDictionary 使用值元组可以提供最佳的线程安全性能,如果您不打算使用来自多个线程的代码 Dictionary 使用值元组也可以很好地工作。

    通常,缓存只会将调用时间减半,并且没有对缓存中的大量数据执行测试,因此性能可能会随着类的增多而降低。我认为不值得缓存结果。

    密码

    Dictionary<Tuple<Type, Type>, bool> cache = new Dictionary<Tuple<Type, Type>, bool>();
    Dictionary<(Type, Type), bool> cache4 = new Dictionary<(Type, Type), bool>();
    ConcurrentDictionary<Tuple<Type, Type>, bool> cache2 = new ConcurrentDictionary<Tuple<Type, Type>, bool>();
    ConcurrentDictionary<(Type, Type), bool> cache3 = new ConcurrentDictionary<(Type, Type), bool>();
    var p = new Dictionary<string, Action>()
    {
        { "no chache", ()=> typeof(F).IsSubclassOf(typeof(A)) },
        {
            "dic cache", ()=>
            {
                var key = Tuple.Create(typeof(F),typeof(A));
                if(!cache.TryGetValue(key, out var value))
                {
                    cache.Add(key, typeof(F).IsSubclassOf(typeof(A)));
                }
            }
        },
        {
            "vtuple + dic cache", ()=>
            {
                var key = (typeof(F),typeof(A));
                if(!cache4.TryGetValue(key, out var value))
                {
                    cache4.Add(key, typeof(F).IsSubclassOf(typeof(A)));
                }
            }
        },
        {
            "concurrent dic cache", ()=>
            {
                cache2.GetOrAdd(Tuple.Create(typeof(F),typeof(A)), (k)=> typeof(F).IsSubclassOf(typeof(A)));
            }
        },
        {
            "vtuple + concurrent + dic cache", ()=>
            {
                cache3.GetOrAdd((typeof(F),typeof(A)), (k)=> typeof(F).IsSubclassOf(typeof(A)));
            }
        },
    };
    
        2
  •  3
  •   Steve Harris    6 年前

    它不会自动缓存。您必须权衡调用的费用与缓存所需的内存。如果有很多这样的检查,我希望字典中的缓存可以提高性能。

        3
  •  2
  •   InBetween    6 年前

    似乎所有其他的答案都有点近视。类型实际上由运行时缓存,但在 Type.BaseType 数量

    在这样的情况下,实际上很容易判断是否存在任何缓存。

    考虑以下代码,并在实际运行之前尝试猜测输出是什么:

    public static void ToCacheOrNotToCache()
    {
        var typeofA = typeof(A);
        var typeofB = typeof(B);
        var getTypeA = new A().GetType();
        var getTypeA2 = new A().GetType();
        var getTypeB = new B().GetType();
        var baseTypeB = getTypeB.BaseType;
    
        Console.WriteLine(
            $"typeof A ref equals getTypeA: {ReferenceEquals(typeofA, getTypeA)}");
        Console.WriteLine(
            $"typeof B ref equals getTypeB: {ReferenceEquals(typeofB, getTypeB)}");
        Console.WriteLine(
            $"typeof A ref equals baseTypeB: {ReferenceEquals(typeofA, baseTypeB)}");
        Console.WriteLine(
            $"getTypeA ref equals getTypeA2: {ReferenceEquals(getTypeA, getTypeA2)}");
    }
    
    class A { }
    class B: A { }
    

    当然,更仔细地检查 类型基本类型 应该提供足够的线索来假设某些缓存正在某种程度上进行; ==(Type, Type) 是赠品; Type 没有值语义,因此如果使用引用相等,则必须意味着每个类型使用一个类型实例。

    代码的输出当然是:

    typeof A ref equals getTypeA: True
    typeof B ref equals getTypeB: True
    typeof A ref equals baseTypeB: True
    getTypeA ref equals getTypeA2: True