David Walker

Why is fFastInjector so fast? Is it thread-safe and otherwise safe?

by David Walker

Why is fFastInjector so fast?

fFastInjector gets its speed from creative use of static variables within a generic static class, optimizer friendly coding, coding to do activities not related to the resolution outside of the resolution piece.

Generic static variables

Generic static variables occur once per type.  Understanding this, I can compute the resolver for <T> and store it in InternalResolver<T>.  So if I compute the resolver for Class1 and put it in InternalResolver<Class1> there is no conflict when later I compute the resolver for Class2 and put it in InternalResolver<Class2>.

When I go to call these resolvers, I am getting the result using the type resolution system.  This is faster than a dictionary lookup for the generic "Resolve<T>()".  For "Resolve(Type type)", I do use a dictionary lookup because that is faster than using reflection to make a method call to "Resolve<T>()"

Optimizer friendly coding

The coding methodology depends on calling InternalResolver<T>.Resolve but most injectors use a syntax that looks more like instance.Resolve<T>().  I don't have any instances but I wanted the syntax to seem more familiar so I take a call to "Injector.Resolve<T>()" and call "InternalResolver<T>.Resolve()".  I want to do more testing of this but I believe it to be optimizer friendly such that the outer call will be optimized away leaving us with one less method call.

Coding unrelated activities outside of the resolution

Anything that can be done outside of the resolver should be.  In the case of the recursion check, I looked at the most common ways to get a recursion error and I trap those by running the recursion check one time on the first resolve after changing the resolver expression.  Once I've done the check, I remove the recursion check so the resolver doesn't have to run it every time.

Why is the InjectorFluent<T> object sometimes (or always in the new release) null?

The interesting thing about the object we use for fluent our fluent methods is that it really doesn't do anything except help us find which static methods to call.  Since extension methods can be called on null objects, we don't even need an instance.  (You do, however, need a "using fFastInjector;" at the top of your file).

Take this code for example:

Injector
                .SetResolver<MyInterface, MyTestClass>()
                .AddPropertyInjector(v => v.MyProperty)
                .AddPropertyInjector(v => v.MyOtherProperty, () => new MyPropertyClass());

SetResolver<MyInterface, MyTestClass> returns an InjectorFluent<MyInterface> object.

The fluent object itself doesn't contain any data.  It's purpose is to allow me to call InternalResolver<MyInterface> and it accomplishes that part just fine by having a null object of type InjectorFluent<MyInterface> and having extensions method to that InjectorFluent<T> that pass those calls to InternalResolver<MyInterface>.

Is the public static object Resolve(Type type) method thread safe?

When you look at the "Resolve(Type type)" method, you will notice that it does a dictionary lookup for a resolver.  If that fails, it call GenericResolve.MakeGenericMethod.

That resembles unsafe code because we never lock the dictionary, never check to see that another thread isn't creating a resolver at the same time.

In the case of Resolve(Type type), the dictionary lookup is only there to speed things up.

The reason any of this is thread-safe is that it depends on the behavior of the initializers for static fields.  The initializer should run once and only once prior to the first access to the field.

The MakeGenericMethod calls InternalResolver<T>.Resolve based on the type we passed in.  If our code happens to do this multiple times before the InternalResolver<T> has completed the initialization and put the resolver in the dictionary, it doesn't conflict, it just processes those calls in a thread-safe manner using the slightly slower method.

Is the check for infinite recursion safe?

It can be defeated.  If your expression to resolve your type changes the concrete type based on some condition, then the infinite recursion check is no good since it only runs on the first resolution after you change the resolver expression.

I have considered evaluating the resolver expression and doing the recursion check every time if the resolver expression contains conditional logic or calls an unknown function, but for now I suggest against using conditional logic in the resolver expression to return differing concrete types.

David Walker

David Walker is a Software Consultant, Photographer, and Digital Artist based out of Orlando, Florida, USA.

He believes in secure reliable software and productive happy teams.

More ...