Skip to content
This repository was archived by the owner on May 16, 2022. It is now read-only.

Repository files navigation

MicroResolver

Extremely Fast Dependency Injection Library.

Features

MicroResolver is desgined for performance. I've released two fastest serializers ZeroFormatter and MessagePack for C#, this library is using there dynamic il code generation technique.

MicroResolver achived fastest at some tests in IoCPerformance benchmark - Singleton(Multithread), Combined(Multithread), Property(Singlethread, Multithread) and other result also top level. This benchmark is nongeneric test(use (object Resove(Type type)), MicroResolver is focused to optimize for generic method(T Resolve<T>()), if using it, faster than other libraries.

Support Features - Consturctor Injection, Field Injection, Property Injection, Method Injection, Collection resolver and Three lifetime support(Singleton, Transient and Scoped).

Quick Start

Install from NuGet(for .NET Framework 4.6, .NET Standard 1.4)

// Create a new containervarresolver=ObjectResolver.Create();// Register interface->type map, default is transient(instantiate every request)resolver.Register<IUserRepository,SqlUserRepository>();// You can configure lifestyle - Transient, Singleton or Scopedresolver.Register<ILogger,MailLogger>(Lifestyle.Singleton);// Compile and Verify container(this is required step)resolver.Compile();// Get instance from containervaruserRepository=resolver.Resolve<IUserRepository>();varlogger=resolver.Resolve<ILogger>();

Notice: MicroResolver requests call Compile before use container.

InjectionAttribute and Resolve Collection

MicroResolver can resolve all public and private properties, fields, constructor and methods. Inject target have to mark [Inject] attribute.

publicclassMyType:IMyType{// field injection[Inject]publicIInjectTargetPublicField;[Inject]IInjectTargetPrivateField;// property injection[Inject]publicIInjectTargetPublicProperty{get;set;}[Inject]IInjectTargetPrivateProperty{get;set;}// constructor injection// if not marked [Inject], the constructor with the most parameters is used.[Inject]publicMyType(IInjectTargetx,IInjectTargety,IInjectTargetz){}// method injection[Inject]publicvoidInitialize1(){}[Inject]publicvoidInitialize2(){}}// and resolve itvarv=resolver.Resolve<IMyType>();

Inject order is Constructor -> Field -> Property -> Method.

If register many types per type, you can use RegisterCollection and Resolve<IEnumerable<T>>.

// Register type -> many typesresolver.RegisterCollection<IMyType>(typeof(T1),typeof(T2),typeof(T3));resolver.Compile();// can resolve by IEnumerbale<T> or T[] or IReadOnlyList<T>.resolver.Resolve<IEnumerable<IMyType>>();resolver.Resolve<IMyType[]>();resolver.Resolve<IReadOnlyList<IMyType>>();// can resolve other type's inject target.publicclassAnotherType{publicAnotherType(IMyType[]targets){}}

Scoped

Lifetime.Scoped is usually the same as Transient but within BeginScope it behaves like a singleton in the scope.

// sample type of check scopepublicclassMyClass:IMyType,IDisposable{publicMyClass(){Console.WriteLine("Created");}publicvoidDispose(){Console.WriteLine("Disposed");}}// -----------varresolver=ObjectResolver.Create();resolver.Register<IMyType,MyClass>(Lifestyle.Scoped);resolver.Compile();using(varcoResolver=resolver.BeginScope(ScopeProvider.Standard)){vari1=coResolver.Resolve<IMyType>();// "Created"vari2=coResolver.Resolve<IMyType>();Console.WriteLine(Object.ReferenceEquals(i1,i2));// "True" -> same instance// if scope end and instantiated types is IDisposable, called Dispose.}// "Disposed"

ScopeProvider has three option in default. ScopeProvider.Standard, ScopeProvider.ThreadLocal and ScopeProvider.AsyncLocal. If needs custom scope, you can create own ScopeProvider.

publicclassMyScopeProvider:ScopeProvider{publicoverridevoidInitialize(IObjectResolverresolver){// when called from BeginScope().}protectedoverrideobjectGetValueFromScoped(Typetype,outboolisFirstCreated){// called per Resolve<T>.}}

Performance Technique - Dynamic IL Inlining

Everyone creates dynamic code generation for optimize performance. But if target is complex type?

// sample of complex dependency typepublicclassForPropertyInjection:IForPropertyInjection{[Inject]publicvoidOnCreate(){}}publicclassForConstructorInjection:IForConsturctorInjection{[Inject]publicIForFieldInjectionMyField;}publicclassComplexType:IComplexType{[Inject]publicIForPropertyInjectionMyProperty{get;set;}publicComplexType(IForConsturctorInjectioninstance1){}[Inject]publicvoidInitialize(){}}// for example, how to resolve ComplexType?varv=resolver.Resolve<IComplexType>();

The following way is not slow, but it is not fastest.

// This is `slow` example of complex type resolvestaticIComplexTypeResolveComplexType(IObjectResolverresolver){vara=resolver.Resolve<IForConsturctorInjection>();varb=resolver.Resolve<IForPropertyInjection>();varresult=newComplexType(a);result.MyProperty=b;result.Initialize();returnresult;}

MicroResolve choose inlining code generation, all dependencies are analyzed and inlined at compile time.

// This is actual code generation of MicroResolver, all dependency is inlined at il code generationstaticIComplexTypeResolveComposite(){vara=newForConstructorInjection();a.MyField=newForFieldInjection();varb=newForPropertyInjection();b.OnCreate();varresult=newComplexType(a);result.MyProperty=b;result.Initialize();returnresult;}

Performance Technique - Generic Type Caching per resolver

The generated code is cached. And how to retrieve it? ConcurrentDictionary? Dictionary? They are slow. MicroResolve choose generic type caching.

This is ObjectResolver signature.

publicabstractclassObjectResolver{publicabstractTResolve<T>();}

If called ObjectResolver.Create, generate dynamic inherited type.

publicclassObjectResolver_Generated:ObjectResolver{publicoverrideTResolve<T>(){// too simple, of course simple is fastest.returnCache<T>.factory();}Cache<T>{// generated Func<T> code is see 'Dynamic IL Inlining' section.publicFunc<T> factory;}}

The code path is too short, it means no overhead.

But generated container can not remove. This is a design constraint.

Performance Technique - Fast NonGeneric lookup table

Type Caching is require to use generics method. But often framework requests nongeneric type.

// fastestresolver.Resolve<T>();// slower but framework requests this method(T)resolver.Resolve(type);

MicroResolver use fast type lookup by own fixed hashtable.

// buckets itemstructHashTuple{publicTypetype;publicFunc<object>factory;}// simplest hash table(fixed-array chaining hashtable)privateHashTuple[][]table;// register - Func<T> -> Func<object> by delegate covariancetable[hash][index]=newFunc<object>(Cache<T>.factory);// simplest == fastest lookuppublicobjectResolve(Typetype){varhashCode=type.GetHashCode();varbuckets=table[hashCode&tableMaskIndex];// table size is power of 2, fast lookup// .Length for loop can remove array bounds checkfor(inti=0;i<buckets.Length;i++){if(buckets[i].type==type){returnbuckets[i].factory();}}thrownewMicroResolverException("Type was not dound, Type: "+type.FullName);}

non-generic lookup is slower than generic but still fast.

Author Info

Yoshifumi Kawai(a.k.a. neuecc) is a software developer in Japan.
He is the Director/CTO at Grani, Inc.
Grani is a mobile game developer company in Japan and well known for using C#.
He is awarding Microsoft MVP for Visual C# since 2011.
He is known as the creator of UniRx(Reactive Extensions for Unity)

Blog: https://medium.com/@neuecc (English)
Blog: http://neue.cc/ (Japanese)
Twitter: https://twitter.com/neuecc (Japanese)

License

This library is under the MIT License.

About

Extremely Fast Dependency Injection Library.

Topics

Resources

Stars

185 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages