Factory relationship type in IoC

Relationship types are a great way to express dependencies and their relationships. In Autofac there is a bunch of implicit relationship types. Still, there are some issues since some of relationships require types which don’t exists in BCL such as Owned<> and IIndex<,>. But a great one which cuts down on boilerplate is the Func<> relationship.

Autofac supports Func<B> and Func<A1...AN, B> factory methods. This is great when you want to repeatedly crate a new instance in your service. For example:

class MyInstanceService : IDisposable { 
  public void DoSomething() { ... } 
  public void Dispose() { ... }
}
class SomeOtherService {
  private readonly Func<MyInstanceService> InstanceFactory;
  public SomeOtherService(Func<MyInstanceService> instanceFactory) {
    this.InstanceFactory = instanceFactory;
  }
  public void RunMe(string arg) {
    using(var inst = InstanceFactory()) {
      ... //use a new instance
    }
  }
}

To set up Autofac we register services into the container.

var builder = new ContainerBuilder();
builder.RegisterType<MyInstanceService>();
builder.RegisterType<SomeOtherService>();
var container = builder.Build();

This all works nice, but what should happen if we registered our service as instance per context/singleton?

var builder = new ContainerBuilder();
builder.RegisterType<MyInstanceService>().SingleInstance();
builder.RegisterType<SomeOtherService>().SingleInstance();
var container = builder.Build();

How should container behave now? Currently Autofac will return the same instance and ouch, our dispose method will be called multiple times. To make it more obvious that this is the wrong behavior, we can use factory with parameters and add disposed check.

class MyInstanceServiceWithArgs : IDisposable { 
  private readonly string Args;
  private bool disposed;
  public MyInstanceService(string args) {
    this.Args = args;
  }
  public void DoSomething() { 
    Console.WriteLine(Args);
  } 
  public void Dispose() { 
    if (disposed)
      throw new ObjectDisposedException("instance");
    disposed = true;
  }
}
class SomeOtherService {
  private readonly Func<string, MyInstanceService> InstanceFactory;
  public SomeOtherService(Func<string, MyInstanceService> instanceFactory) {
    this.InstanceFactory = instanceFactory;
  }
  public void RunMe(string arg) {
    using(var inst = InstanceFactory(arg)) {
      inst.DoSomething();
    }
  }
}

It’s obvious now that it’s wrong to reuse previous instance, since it will have different Args field value (and will write to console wrong value) and worse it will throw ObjectDisposedException.

What are the consequences if we fix this? Well, we are using customized version of Autofac (with some optimization and bug fixes) and were hit with a strange error shortly after we’ve “fixed” this by disallowing shared instanced for factory resolutions. Let’s use the same services again:

class MyInstanceService : IDisposable {
  public void DoSomething() { ... }
  public void Dispose() { ... }
}
class SomeOtherService {
  public SomeOtherService(Func<MyInstanceFactory> factory) { ... }
  public void RunMe(string arg) { ... }
}

And register it as an instance:

var builder = new ContainerBuilder();
builder.RegisterType<MyInstanceService>().SingleInstance();
builder.RegisterType<SomeOtherService>().SingleInstance();
var container = builder.Build();

When we try to resolve SomeOtherService we’ll get an exception saying that MyInstanceFactory is not registered. Why is that?
Well, we want to create a new instance of MyInstanceFactory, but registration which will allow us to do that doesn’t actually exist. We need to register stuff in the container with transient scope

var builder = new ContainerBuilder();
builder.RegisterType<MyInstanceService>();
builder.RegisterType<SomeOtherService>().SingleInstance();
var container = builder.Build();

which will allow new instances for MyInstanceService. It looks kind of strange that we are unable to resolve instance type which we registered, but when you consider the relationship type, it becomes clearer why it behaves like that.
So, in the end Autofac will behave the same way with this “correct” registration, but the “fixed” version will not support same instance resolution from factory, but rather throw resolution errors.

Leave a Reply

Your email address will not be published. Required fields are marked *