It's quite a common pattern to have a generic interface that is implemented by a number of concrete types and to want to be able to instantiate the concrete implementation by the type of generic parameter.
For example, given an interface IPat<T>, concrete classes Cat and Dog, we might have CatPatter implementing IPat<Cat> and DogPatter implementing IPat<Dog>. With no IoC container, to pat our pet based on the type of pet, our code needs the explicit knowledge that the CatPatter and DogPatter classes implement IPat<T>.
With an IoC container, we can register the types so all we need to do is ask the container for an instance of IPat<T> for out type of pet, but (at least with Unity) we still need to manually register each implementation of the IPat<> interface. This is clearly not ideal!
With a bit of reflection funkery, we can automatically register types that implement a generic interface.
1 public static void RegisterAllTypesForOpenGeneric(this IUnityContainer container, Type openGenericType, Assembly targetAssembly)
2 {
3 if (!openGenericType.IsGenericTypeDefinition)
4 throw new ArgumentException("typeToRegister must be an open generic type", "typeToRegister");
5
6 foreach (Type type in targetAssembly.GetExportedTypes())
7 {
8 if (openGenericType.IsInterface)
9 RegisterInterfaceTypes(container, openGenericType, type, type);
10 else
11 RegisterBaseTypes(container, openGenericType, type, type);
12 }
13 }
14
15 private static void RegisterInterfaceTypes(IUnityContainer container, Type openGenericType, Type targetType, Type typeToRegister)
16 {
17 foreach (Type interfaceType in targetType.GetInterfaces())
18 if (interfaceType.IsGenericType && !interfaceType.ContainsGenericParameters && openGenericType.IsAssignableFrom(interfaceType.GetGenericTypeDefinition()))
19 container.RegisterType(interfaceType, typeToRegister);
20 }
21
22 private static void RegisterBaseTypes(IUnityContainer container, Type openGenericType, Type targetType, Type typeToRegister)
23 {
24 if (targetType.BaseType != null && targetType.BaseType != typeof(object))
25 if (targetType.BaseType.IsGenericType && openGenericType.IsAssignableFrom(targetType.BaseType.GetGenericTypeDefinition()))
26 container.RegisterType(targetType.BaseType, typeToRegister);
27 else
28 RegisterBaseTypes(container, openGenericType, targetType.BaseType, typeToRegister);
29 }
and some unit tests to make sure all is playing the game:
1 [TestClass]
2 public class AutoRegistrationTest
3 {
4 [TestMethod]
5 public void WhenPassedOpenGenericInterfaceType_AllDerivedTypesAreRegistered()
6 {
7 // use a strict mock to ensure no other types are registered
8 var container = MockRepository.GenerateStrictMock<UnityContainerBase>();
9 container.Expect(x => x.RegisterType(typeof(ITest<A>), typeof(TestA))).Return(container);
10 container.Expect(x => x.RegisterType(typeof(ITest<B>), typeof(TestB))).Return(container);
11 container.RegisterAllTypesForOpenGeneric(typeof(ITest<>));
12 }
13
14 [TestMethod]
15 public void WhenPassedOpenGenericConcreteType_AllDerivedTypesAreRegistered()
16 {
17 // use a strict mock to ensure no other types are registered
18 var container = MockRepository.GenerateStrictMock<UnityContainerBase>();
19 container.Expect(x => x.RegisterType(typeof(TestBase<A>), typeof(TestA))).Return(container);
20 container.Expect(x => x.RegisterType(typeof(TestBase<B>), typeof(TestB))).Return(container);
21 container.RegisterAllTypesForOpenGeneric(typeof(TestBase<>));
22 }
23
24 [TestMethod]
25 public void WhenBaseClassImplementsGenericType_DerivedTypesAreRegistered()
26 {
27 // use a strict mock to ensure no other types are registered
28 var container = MockRepository.GenerateStrictMock<UnityContainerBase>();
29 container.Expect(x => x.RegisterType(typeof(ITestBase<A>), typeof(TestBaseWithInterfaceA))).Return(container);
30 container.Expect(x => x.RegisterType(typeof(ITestBase<B>), typeof(TestBaseWithInterfaceB))).Return(container);
31 container.RegisterAllTypesForOpenGeneric(typeof(ITestBase<>));
32 }
33 }
34
35 public interface ITest<T> { }
36 public class TestBase<T> { }
37
38 public class TestA : TestBase<A>, ITest<A> { }
39 public class TestB : TestBase<B>, ITest<B> { }
40
41 public class A { }
42 public class B { }
43
44 public class TestBaseWithInterface<T> : ITestBase<T> { }
45 public interface ITestBase<T> { }
46 public class TestBaseWithInterfaceA : TestBaseWithInterface<A> { }
47 public class TestBaseWithInterfaceB : TestBaseWithInterface<B> { }
Sorry about the formatting. Ergh. UPDATE: Formatting is still not ideal, I used CopySourceAsHTML plugin for VS2008. I can't install anything here at work, so no Live Writer :-\
Labels: IoC, unity