The IIndex<K,V> Relationship Type

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<T>, 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.

  • Great idea 🙂

    Is there a test-friendly version? For example, when I take a dependency on a Func, it’s easy to pass in something like () => mock.Object. How would a test for an object using IIndex look?

  • Hey thanks for dropping by, Paul. An IIndex is pretty easy to mock using typical interface-based mocking, e.g. with Moq along the lines of:

    var mockIndex = new Mock<IIndex<Foo,Bar>>();
    mockIndex.SetupGet(i => i[someFoo]).Returns(someBar);
    

    Whenever possible I avoid the magic of mocks (call me old fashioned!) so I’d be more inclined to use:

    class IndexDictionary<K,V> : Dictionary<K,V>, IIndex<K,V> {}
    

    Thus:

    var index = new IndexDictionary<Foo,Bar>();
    index.Add(someFoo, someBar);
    

    Interface implementation with base class members in C# is ***wonderful*** 🙂

  • I guess I was kinda hoping you’d ship an `IndexDictionary` out of the box for me, so I wouldn’t have to write it myself – I’m `Lazy` after all 😉

  • Hey, how do I include code samples in comments on your blog?

  • You’re going to laugh, it is pretty primitive:

    <pre>
    Code goes here..
    </pre>

  • Damian

    I love the overall idea of this but the problem I have with this is that it uses an Autofac specific interface to “free” us from the container. So now I am not referencing the container directly but I still need to reference Autofac in my assemblies that use this (which is not the case for the Func and Lazy relationships). Is there a reason you didn’t opt for IDictionary, Func, or even IEnumerable<KeyValuePair> considering the binding of Autofac?

  • G’day Damian,

    Thanks for your feedback, you’ve raised some important points.

    Regarding the types you suggested, yes, those were considered. Func<…> already has a natural binding in Autofac (creation and parameterised creation); IDictionary<,> is both enumerable and mutable (see the comments on ILookup<,> above) and IEnumerable<KeyValuePair<,>> is enumerable.

    The solutions to these issues are combined, and there are a few options.

    First, if you don’t want to use any particular relationship type from Autofac in your code, pick the type that you do want to use then register an adapter from the Autofac type to your chosen (possibly domain-specific) type.

    For IIndex<,> and types like it, you can create a component that depends on the Autofac type and implements a domain-specific interface, package that type up with your container configuration.

    I’ve just added some notes on this to the relationship types wiki page:

    The custom relationship types in Autofac don’t force you to bind your application more tightly to Autofac. They give you a programming model for container configuration that is consistent with the way you write other components (vs. having to know a lot of specific container extension points and APIs that also potentially centralise your configuration.)

    Second, there is more than one way of interpreting “don’t reference the container from your code.” The interpretation you’ve mentioned (assembly references) doesn’t necessarily have any impact on the quality of your application code, at least not in a measurable way. How types are packaged makes very little difference once the loader has found them and pulled them into the running CLR. Refactoring and navigation tools also work at the type level and are not impacted by how types are partitioned between assemblies.

    The way I personally interpret “don’t reference the container” is don’t use the container as a Service Locator. This usage pattern does have a significant impact on long-term code quality, and that’s something Autofac is working hard to prevent.

    So to sum up – you can choose your own types using either Autofac adapters or hand-written ones – this is completely your choice. On the other hand, if you buy my argument about assembly references vs. service location, you can just stop worrying and use IIndex<,>, knowing that it is a simple type to pull out should your taste in IoC containers change 😉

    Thanks again for your comment.
    Nick

    P.S. there’s a separate project underway to define and deploy a set of relationship types separately from Autofac, but it will take a while. Perhaps in the meantime the .NET Framework will fill some of the gaps too.

  • Damian Reeves

    Nicholas,
    I’m trying to follow your suggestion from your comments about adapting my own relationship types. So how would I adapt an open generic? This doesn’t seem possible:
    I have my own IIndex interface (lets call it IComponentIndex) which I plan on adapting using a class called IndexAdapter, but there is no way for me to register this in an open generic manner. Do you have any suggestions?

  • Hi Damian,

    You can achieve this by implementing an IRegistrationSource – info should be findable from here: http://code.google.com/p/autofac/wiki/RegistrationSources

    Cheers,
    Nick

  • Pingback: IoC containers: where you define the seams of applications | Nathan Evans' Nemesis of the Moment()