Hiro claims to be the fastest container. This statement is based on the benchmark given by the author (see here for an objective comparison between many containers). Whether or not this benchmark is realistic depends upon the size of your application. The benchmark seems to be cleverly setup with a very small set of registered types. When adding more registrations to the benchmark, the performance starts dropping (see below for an example benchmark of this). When looking closely we can see that Hiro has a performance characteristic of O(n), while normal DI frameworks have a characteristic of O(1), thus with other frameworks the performance stays constant with the number of registered types.
What’s nice about Hiro is that it generates a new assembly on the fly and resolving a new type consists of just a single interface call. This is very fast. Most common DI frameworks on the other hand, will always have to do a dictionary lookup during a call to GetInstance. Hiro’s GetInstance method is basically a big switch case statement internally, implemented as a bunch of if-statements. If-based switch case statements are extremely fast up until a point. The C# compiler uses an heuristic of (I believe) 18 case statements as turning point. Below that number the compiler generates a bunch of if-statements. Above that number it creates and stores a static dictionary and does a dictionary lookup, because above 18 lookups performance of a dictionary lookup if simply faster.
Well guess what; Hiro doesn’t use the optimization as the C# compiler does. That's why the performance keeps dropping as more and more types are added to the container. A linear performance characteristic –or O(n) for short- is okay for small sets of data, but any well written and commonly sized, dependency-friendly application has many type registrations / mappings. And in that case the performance of Hiro drops quickly.
So, to answer your questions:
Is it still the fastest IOC container
today?
For very small applications it is the fastest. For any normal sized applications it has never been the fastest.
Is it ready for production?
Hard to say. Its current status is alpha and it misses a lot of features that other IOC framework have. The developer just (see comments below) published a stable release. This means that according to the developer it is ready for production.
What are its major advantages
and disadvantages over other IOC containers?
Take a look for instance at this article (and the follow up) which gives a good (feature) comparison of IOC frameworks. The writer of the article has a vision of what he thinks is important. You have to make such a list for yourself. Performance seems to be high on your list. However, please note that there are other ways to improve performance. For instance by registering types as singletons to prevent the creation of many types.
Here is a little comparison with other containers. Note that I didn't write Hiro, so I could be missing things, especially since there seems to be no documentation at all, but here goes:
- You need 4 assemblies to run, instead of 1 or 2 for most IOC frameworks.
- It throws a stack overflow when a recursive dependency is resolved (most frameworks do this btw). So there is no cyclic dependency detection.
Doesn't seem to support any lifestyle other than transient (the writer does say it will, but I currently see no support for this). This actually is bad for performance, because most services would normally be registered as singletons. According to the author, it supports multiple lifestyles.
- Doesn't support resolving open generic types.
- Doesn't support unregistered type resolution.
- I has no documentation accept XML (intellisense) documentation.
Are there any other containers can do
IOC at compile time?
Define 'compile time'. Hiro generates a new assembly on the fly once during runtime. Others (like Autofac, Windsor and Simple Injector) emit IL or compile delegates under the covers. This isn't any slower than compiling the complete assembly in one go (however, there is the overhead of calling the delegate which is a tiny bit slower than making an interface call).
BTW, I changed the benchmark to see the behavior described above appear. With 50 extra types registered you will see that Hiro performs already three times as slow as with the initial benchmark. Here is the code I used:
public interface IHandler<T> { }
public class Handler<T> : IHandler<T> { }
public class HiroUseCase : UseCase
{
IMicroContainer container;
private static void RegisterHandler<T>(DependencyMap map)
{
map.AddService(typeof(IHandler<T>), typeof(Handler<T>));
}
public HiroUseCase()
{
var map = new DependencyMap();
// *** My added registrations
RegisterHandler<byte>(map);
RegisterHandler<byte?>(map);
RegisterHandler<short>(map);
RegisterHandler<short?>(map);
RegisterHandler<ushort>(map);
RegisterHandler<ushort?>(map);
RegisterHandler<int>(map);
RegisterHandler<int?>(map);
RegisterHandler<uint>(map);
RegisterHandler<uint?>(map);
RegisterHandler<long>(map);
RegisterHandler<long?>(map);
RegisterHandler<ulong>(map);
RegisterHandler<ulong?>(map);
RegisterHandler<float>(map);
RegisterHandler<float?>(map);
RegisterHandler<double>(map);
RegisterHandler<double?>(map);
RegisterHandler<decimal>(map);
RegisterHandler<decimal?>(map);
RegisterHandler<DateTime>(map);
RegisterHandler<DateTime?>(map);
RegisterHandler<char>(map);
RegisterHandler<char?>(map);
RegisterHandler<object>(map);
RegisterHandler<string>(map);
RegisterHandler<bool>(map);
RegisterHandler<bool?>(map);
RegisterHandler<Enum>(map);
RegisterHandler<DateTimeKind>(map);
RegisterHandler<DateTimeKind?>(map);
RegisterHandler<DateTimeOffset>(map);
RegisterHandler<DateTimeOffset?>(map);
RegisterHandler<DayOfWeek>(map);
RegisterHandler<DayOfWeek?>(map);
RegisterHandler<DBNull>(map);
RegisterHandler<Delegate>(map);
RegisterHandler<DivideByZeroException>(map);
RegisterHandler<DllNotFoundException>(map);
RegisterHandler<Exception>(map);
RegisterHandler<KeyNotFoundException>(map);
RegisterHandler<InvalidOperationException>(map);
RegisterHandler<InvalidCastException>(map);
RegisterHandler<InvalidProgramException>(map);
RegisterHandler<InvalidTimeZoneException>(map);
RegisterHandler<IDisposable>(map);
RegisterHandler<IComparable>(map);
RegisterHandler<IEquatable<int>>(map);
RegisterHandler<IEnumerable>(map);
RegisterHandler<IEqualityComparer>(map);
// *** Original benchmark setup
map.AddService(typeof(IWebApp), typeof(WebApp));
map.AddService(typeof(IAuthenticator), typeof(Authenticator));
map.AddService(typeof(IStockQuote), typeof(StockQuote));
map.AddService(typeof(IDatabase), typeof(Database));
map.AddService(typeof(IErrorHandler), typeof(ErrorHandler));
map.AddService(typeof(ILogger), typeof(Logger));
IContainerCompiler compiler = new ContainerCompiler();
var assembly = compiler.Compile(map);;
var loadedAssembly = assembly.ToAssembly();
var containerType = loadedAssembly.GetTypes()[0];
container = (IMicroContainer)Activator
.CreateInstance(containerType);
}
public override void Run()
{
var webApp =
(IWebApp)container.GetInstance(typeof(IWebApp), null);
webApp.Run();
}
}