Service Locator Revisited

I guess I have awakened a slumbering giant by writing the post the other day. I went back to add more functionality to the Service Locator (to enable per transaction and per thread services). Before I started breaking stuff by adding new functionality, I decided to make unit tests for the existing stuff. My first test was simple enough, register and retrieve a service from the ServiceContainer (which might need to be renamed because of a collision with System.ComponentModel.Design.ServiceContainer).

[TestMethod]
public void CanRegisterService()
{
    var dummyService = new DummyService();
    ServiceContainer.RegisterService<IDummyService>(dummyService);
    var retrievedService = ServiceContainer.GetService<IDummyService>();
    Assert.IsInstanceOfType(retrievedService, typeof(IDummyService),"The returned service is not of the correct type");
    Assert.AreEqual(dummyService,retrievedService,"The returned service is a different instance.");
}

Simple enough right? Let’s run it and make sure it’s green and move on…to…hey why is it failing? What happened? After a quick bit of investigation, I found that the problem lay in RegisterService overload that takes the Func:

public static void RegisterService<TService>(Func<TService> creator)
{
    _Container.AddService(creator);
}

Do you see it? Maybe the implementation of AddService might make it more obvious

private void AddService<TService>(TService instance)
{
    _ServicesLock.EnterUpgradeableReadLock();
    try
    { 
        if (!_Services.ContainsKey(typeof(TService))) 
        { 
            _ServicesLock.EnterWriteLock(); 
            try 
            { 
                _Services[typeof(TService)] = instance; 
            } 
            finally 
            { 
                _ServicesLock.ExitWriteLock(); 
            } 
        } 
    } 
    finally 
    {
        _ServicesLock.ExitUpgradeableReadLock(); 
    } 
}

Because of type inference, <TService> is Func<TService> when we try to retrieve it we’re looking for TService as the key, and of course we won’t find it. So there’s a lesson to be learned here. Actually there are two.

  1. It’s the simplest of changes that open the door for the most insidious bugs (not that this bug is very insidious)
  2. If it’s worth writing it’s worth testing. No matter how small the functionality is, you should write a test for it because when it breaks you want to know.

Anyway, now that I’ve got the tests in place for the existing functionality, I’m going to go ahead and see about implementing per transaction life management. But not tonight.

Published Thursday, August 13, 2009 12:18 AM by Mike Brown

Leave a Comment

(required) 
(required) 
(optional)
(required)