Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is type.isSubclassOf(Type otherType) cached or do I have to do that myself?

simple Question:

is type.isSubclassOf(Type otherType) somewhere cached as a let's say Dictionary<Tuple<Type, Type>, bool>?

If not, how expensive is such a call?

I'm quite often checking for that to keep my code extendable and am converting methods I use the most into dictionaries...

like image 790
Christoph Wolf Avatar asked Dec 06 '25 04:12

Christoph Wolf


1 Answers

While it's not ideal do depend on implementation details, we can take a look at the code of IsSubclassOf using 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;
}

So the short answer is that in this version of the framework (4.6) the call is not cached, and it implies walking up the inheritance hierarchy.

The question of how expensive the call is depends on your use case. You should measure whether your code spends a significant amount of time in this method and whether a cache helps.

Performance

The question of whether it is worth caching the result, is one of measuring the amount of time the call takes vs a cache lookup. I tested 5 scenarios:

  1. Direct invocation
  2. Cache using: Dictionary<Tuple<Type, Type>, bool>
  3. Cache using: Dictionary<(Type, Type), bool>(value tuple)
  4. Cache using: ConcurrentDictionary<Tuple<Type, Type>, bool>
  5. Cache using: ConcurrentDictionary<(Type, Type), bool> (value tuple)

Results

  1. Direct invocation - 0.15s/ call
  2. Cache using: Dictionary<Tuple<Type, Type>, bool> - 0.12s / call
  3. Cache using: Dictionary<(Type, Type), bool> - 0.06s / call
  4. Cache using: ConcurrentDictionary<Tuple<Type, Type>, bool> - 0.13s/call
  5. Cache using: ConcurrentDictionary<(Type, Type), bool> (value tuple) - 0.7s/call

ConcurrentDictionary with value tuples offers the best thread safe performance, if you don't plan to use the code from multiple threads a simple Dictionary with value tuples also works very well.

Generally the cache only halves the call time and the test was not performed for large amount of data in the cache so performance may degrade with more classes. I don't think it's worth caching the result.

Code

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)));
        }
    },
};
like image 131
Titian Cernicova-Dragomir Avatar answered Dec 07 '25 20:12

Titian Cernicova-Dragomir



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!