Extremely Fast Dependency Injection Library.
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).
Install from NuGet(for .NET Framework 4.6, .NET Standard 1.4)
- Install-Package MicroResolver
// Create a new container
var resolver = ObjectResolver.Create();
// Register interface->type map, default is transient(instantiate every request)
resolver.Register<IUserRepository, SqlUserRepository>();
// You can configure lifestyle - Transient, Singleton or Scoped
resolver.Register<ILogger, MailLogger>(Lifestyle.Singleton);
// Compile and Verify container(this is required step)
resolver.Compile();
// Get instance from container
var userRepository = resolver.Resolve<IUserRepository>();
var logger = resolver.Resolve<ILogger>();
Notice: MicroResolver requests call Compile
before use container.
MicroResolver can resolve all public and private properties, fields, constructor and methods. Inject target have to mark [Inject]
attribute.
public class MyType : IMyType
{
// field injection
[Inject]
public IInjectTarget PublicField;
[Inject]
IInjectTarget PrivateField;
// property injection
[Inject]
public IInjectTarget PublicProperty { get; set; }
[Inject]
IInjectTarget PrivateProperty { get; set; }
// constructor injection
// if not marked [Inject], the constructor with the most parameters is used.
[Inject]
public MyType(IInjectTarget x, IInjectTarget y, IInjectTarget z)
{
}
// method injection
[Inject]
public void Initialize1()
{
}
[Inject]
public void Initialize2()
{
}
}
// and resolve it
var v = 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 types
resolver.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.
public class AnotherType
{
public AnotherType(IMyType[] targets)
{
}
}
Lifetime.Scoped
is usually the same as Transient but within BeginScope it behaves like a singleton in the scope.
// sample type of check scope
public class MyClass : IMyType, IDisposable
{
public MyClass()
{
Console.WriteLine("Created");
}
public void Dispose()
{
Console.WriteLine("Disposed");
}
}
// -----------
var resolver = ObjectResolver.Create();
resolver.Register<IMyType, MyClass>(Lifestyle.Scoped);
resolver.Compile();
using (var coResolver = resolver.BeginScope(ScopeProvider.Standard))
{
var i1 = coResolver.Resolve<IMyType>(); // "Created"
var i2 = 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.
public class MyScopeProvider : ScopeProvider
{
public override void Initialize(IObjectResolver resolver)
{
// when called from BeginScope().
}
protected override object GetValueFromScoped(Type type, out bool isFirstCreated)
{
// called per Resolve<T>.
}
}
Everyone creates dynamic code generation for optimize performance. But if target is complex type?
// sample of complex dependency type
public class ForPropertyInjection : IForPropertyInjection
{
[Inject]
public void OnCreate()
{
}
}
public class ForConstructorInjection : IForConsturctorInjection
{
[Inject]
public IForFieldInjection MyField;
}
public class ComplexType : IComplexType
{
[Inject]
public IForPropertyInjection MyProperty { get; set; }
public ComplexType(IForConsturctorInjection instance1)
{
}
[Inject]
public void Initialize()
{
}
}
// for example, how to resolve ComplexType?
var v = resolver.Resolve<IComplexType>();
The following way is not slow, but it is not fastest.
// This is `slow` example of complex type resolve
static IComplexType ResolveComplexType(IObjectResolver resolver)
{
var a = resolver.Resolve<IForConsturctorInjection>();
var b = resolver.Resolve<IForPropertyInjection>();
var result = new ComplexType(a);
result.MyProperty = b;
result.Initialize();
return result;
}
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 generation
static IComplexType ResolveComposite()
{
var a = new ForConstructorInjection();
a.MyField = new ForFieldInjection();
var b = new ForPropertyInjection();
b.OnCreate();
var result = new ComplexType(a);
result.MyProperty = b;
result.Initialize();
return result;
}
The generated code is cached. And how to retrieve it? ConcurrentDictionary? Dictionary? They are slow. MicroResolve choose generic type caching.
This is ObjectResolver
signature.
public abstract class ObjectResolver
{
public abstract T Resolve<T>();
}
If called ObjectResolver.Create
, generate dynamic inherited type.
public class ObjectResolver_Generated : ObjectResolver
{
public override T Resolve<T>()
{
// too simple, of course simple is fastest.
return Cache<T>.factory();
}
Cache<T>
{
// generated Func<T> code is see 'Dynamic IL Inlining' section.
public Func<T> factory;
}
}
The code path is too short, it means no overhead.
But generated container can not remove. This is a design constraint.
Type Caching is require to use generics method. But often framework requests nongeneric type.
// fastest
resolver.Resolve<T>();
// slower but framework requests this method
(T)resolver.Resolve(type);
MicroResolver use fast type lookup by own fixed hashtable.
// buckets item
struct HashTuple
{
public Type type;
public Func<object> factory;
}
// simplest hash table(fixed-array chaining hashtable)
private HashTuple[][] table;
// register - Func<T> -> Func<object> by delegate covariance
table[hash][index] = new Func<object>(Cache<T>.factory);
// simplest == fastest lookup
public object Resolve(Type type)
{
var hashCode = type.GetHashCode();
var buckets = table[hashCode & tableMaskIndex]; // table size is power of 2, fast lookup
// .Length for loop can remove array bounds check
for (int i = 0; i < buckets.Length; i++)
{
if (buckets[i].type == type)
{
return buckets[i].factory();
}
}
throw new MicroResolverException("Type was not dound, Type: " + type.FullName);
}
non-generic lookup is slower than generic but still fast.
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)
This library is under the MIT License.