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...
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:
Dictionary<Tuple<Type, Type>, bool>Dictionary<(Type, Type), bool>(value tuple) ConcurrentDictionary<Tuple<Type, Type>, bool>ConcurrentDictionary<(Type, Type), bool> (value tuple)Results
Dictionary<Tuple<Type, Type>, bool> - 0.12s / callDictionary<(Type, Type), bool> - 0.06s / callConcurrentDictionary<Tuple<Type, Type>, bool> - 0.13s/callConcurrentDictionary<(Type, Type), bool> (value tuple) - 0.7s/callConcurrentDictionary 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)));
}
},
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With