Component metadata, which is consumed via the Lazy<T,M>, Meta<M> or Meta<T,M> relationship types, gives us a way to choose between available components based on some criteria:

class Browser
{
    IEnumerable<Lazy<IViewer, IViewerMetadata>> _viewers;
    
    public Browser(IEnumerable<Lazy<IViewer, IViewerMetadata>> viewers)
    {
        _viewers = viewers;
    }
    
    public void OnViewFile(string filename)
    {
        var ext = Path.GetExtension(filename);
        var viewer = _viewers.Single(v => v.Metadata.FileTypes.Contains(ext));
        viewer.Value.View(filename);
    }
}

Components are selected by examining the metadata associated with them. When fast lookup is required this technique is not optimal, because unless an index is built by hand, choosing a component based on metadata requires a linear search.

One of the goals of the “relationship types” approach, of which metadata is an example, is to provide a way to consume the services of an IoC container without falling back to the problematic Service Locator pattern. Requiring an O(n) lookup in the process seems unreasonable when there is a large number of components, as Service Locators can do much better.

To address this situation, Autofac provides the Autofac.Features.Indexed.IIndex<K,V> type. In Autofac 2.3 some work has been done to better integrate this into the container, so now seems like a good time to explain in more detail how it works.

Objects as Keys

For at least as long as I’ve been using them, IoC containers have supported two primary ways of looking up a component. The most common is by type:

builder.Register<Renderer>().As<IRenderer>();
// then
var r = container.Resolve<IRenderer>();

The other is by name (or by name-and-type):

builder.Register<FastRenderer>().Named<IRenderer>("Fast");
// then
var fr = container.Resolve<IRenderer>("Fast");

Resolving by name and type drives all sorts of common IoC scenarios – finding handlers for messages, controllers for routes and so-on.

Autofac stuck to this model, but down the track I realised that requiring the use of a string for the key is unnecessary. Consider the case where magic strings are eliminated elsewhere in the application in favour of an enum:

enum RenderSpeed { Slow, Fast }

Using this enum with the string-based component selection API is clunky:

builder.Register<FastRenderer>()
    .Named<IRenderer>(RenderSpeed.Fast.ToString());
// then
var fr = container.Resolve<IRenderer>(RenderSpeed.Fast.ToString());

The ToString() calls can easily be eliminated by accepting arbitrary objects, here the enum values, as keys. The above code becomes:

builder.Register<FastRenderer>()
    .Keyed<IRenderer>(RenderSpeed.Fast);
// then
var fr = container.Resolve<IRenderer>(RenderSpeed.Fast);

In many situations where components are to be selected, there’s a better key type than string, so the addition of Keyed() is one I’ve been happy with.

Looking-up Keyed Services with an Index

The examples so far use Resolve() for the purposes of illustration; there’s a better way to use keys, and that’s via IIndex<K,V>.

public interface IIndex<TKey, TValue>
{
    TValue this[TKey key] { get; }
    bool TryGetValue(TKey key, out TValue value); 
}

This type is an adapter provided automatically by the container, just as the other relationship types are. It is parameterised by the type of the key and the type of the implementation.

This example is taken from a recent question about the State Pattern on StackOverflow – you can see some of the context over there.

public class Modem : IHardwareDevice
{
    IIndex<DeviceState, IDeviceState> _states;
    IDeviceState _currentState;

    public Modem(IIndex<DeviceState, IDeviceState> states)
    {
         _states = states;
         SwitchOn();
    }

    void SwitchOn()
    {
         _currentState = _states[DeviceState.Online];
    }
} 

In the SwitchOn() method, the index is used to find the implementation of IDeviceState that was registered with the DeviceState.Online key.

Isn’t this a kind of Service Locator, anyway?

I was at first a little hesitant to go down this path because of the apparent similarity between looking up implementations with an index and using a Service Locator.

To cut a long story short, there’s only a superficial likeness. The important differences between this and the Service Locator approach are:

  • An index is selective, both about the kind of thing being looked up and the meaning of the key; most issues with Service Locators come from them being too broad, and I don’t think that is the case here

  • IIndex<K,V> is a subset of IDictionary<K,V> so if you’re going to go after indexes in your anti-Service-Locator rampage then you’ll have to wipe out your dictionaries, too

Still, feel free to get in there and flame me if you think this is heretical.

Why not IQueryable, or ILookup<K,V>?

There’s a type in .NET 3.5 called ILookup<K,V> that could have fulfilled the role of IIndex<K,V> but unfortunately it assumes that all of the keys can be enumerated. IIndex<K,V> isn’t an enumerable type, because in order to provide that on top of Autofac’s component model, all the possible key values would need to be known by the container in advance.

IQueryable<T> is another candidate for providing optimised lookup. The essential technique would be to use it in combination with metadata, in the same way that IEnumerable<T> is used, but to build and use indexes behind the scenes to ‘smartly’ handle the expression trees corresponding to the selection predicates. Now, just to explain this is getting complicated; as attractive as this option might seem, the implementation complexity is so hideously huge by comparison that I don’t think we could ever expect it to be stable.

Conclusions

Metadata is a very flexible and elegant mechanism; it enables a much richer dependency model than keys and indexes, and supports more scenarios. Indexes and keys have their own place though, and I’d expect that if you add them to your Autofac toolbox you’ll find them useful more than once.