Internal registrations dictionary filled with null values.

Aug 29, 2012 at 10:30 AM

This question was originaly asked by kimsagro and was migrated from this thread. 

it appears that just querying the container for a service [using GetRegistration] appears to add it to the registrations dictionary with a null InstanceProducter. So using the above code in a WebAPI solution causes a whole bunch of additional entries in the registration dictionary as the framework queryies to see if you have registered (overriden) any of their internal services.

Aug 29, 2012 at 10:30 AM
Edited Sep 5, 2012 at 3:33 PM

Your observation is correct. When GetInstance, GetService or GetRegistration are called, the container will look first for an existing registration. If it does not exist it will try to build a registration for that type, because even without an explicit registration, the container might be able to return the requested type. There are three reasons why this could happen.

  1. The container returns a new instance of an unregistered concrete type.
  2. The container returns an empty collection of an unregistered IEnumerable<T>.
  3. A type could be returned using unregistered type resolution. The container contains a ResolveUnregisteredType event, that allows you to do a 'last minute' registration of an unregistered type. Features such as the RegisterOpenGeneric extension method, are built upon this event.

This process is very performance intensive, even if the final result is no registration. Because of this, the result is always cached, even if the type can not be resolved. In that case the internal dictionary is updated with a null reference for that specific type. This prevents from going through this process over and over again (this actually drained the performance of one of the early beta's). From a performance perspective, it doesn't matter how big the dictionary gets, since getting elements from an dictionary is constant: O(1) (assuming that the size of the array does not impact the number of cache misses, of course).

The assumption with this optimization is that number of 'invalid' requests (requests that result in a null registration) is finite. The extra amount of memory for each null registration is small (16 bytes), but if you are doing crazy stuff, the size can grow. Take a look at this (quite silly) example:

int somethingSilly = (
    from assembly in AppDomain.CurrentDomain.GetAssemblies()
    from type in assembly.GetExportedTypes()
    where container.GetRegistration(type) != null
    select type)

In this example all public types of all loaded assemblies is iterated and GerRegistration is called on each and every one of them. Let's say you referenced all System.* assemblies in the .NET framework (something you'll never do for a production system). In that (extreme) case, the dictionary will contain about 11,800 entries. In that case the internal array will have 12,143 slots (which is the next prime number). Each slot will be 16 bytes, and the size of the internal array will therefore be around 190 KB (which is, if you think about it, not that bad). However, you will hopefully never write code like this. When working with MVC's IDependencyResolver and WebAPI's IDependencyResolver, the framework will fall back to the default behavior when you didn't register the type. This however, will only result in at most a few dozen 'null' registrations, which will not have a noticeable impact on the memory use of your application.

Marked as answer by dot_NET_Junkie on 11/4/2013 at 2:01 AM