15分钟内实现IoC容器






4.78/5 (39投票s)
如何从头开始创建一个非常简单、基础的IoC容器。
引言
上周,我正在编写一个 Compact Framework 应用程序。我现在非常习惯 IoC,几乎无法想象用另一种方式编程。问题在于 Windsor(我最喜欢的)容器无法在 CF 上运行。所以我查看了我的选择。结果并不多。实际上我找到了一些。其中之一:Ninject,虽然很有前途,但需要一些研究,因为它不是一个容器,而且我当时没有时间。还有另外几个,它们的工作方式与 Windsor 类似,但它们在解析泛型组件时遇到了问题。所以我认为:我要自己动手!!!
变更
- 2011 年 6 月 18 日:
IService
接口没什么用,如果说有用的话。所以,对它的每次使用都已经被object
替换。对于第一次看到这篇文章的读者:没关系。 - 2011 年 6 月 18 日:改进了泛型注册,之前出现了一些问题,现在已经解决了。
- 2012 年 5 月 15 日: 添加了关于 IoC 和 DI 的参考资料。
从头开始构建 IoC
让我们从接口开始
public interface IServicesContainer
{
TService Resolve<TService>();
object Resolve(Type tService);
}
public interface IServicesRegistrar : IServicesContainer
{
void RegisterForAll(params Type[] tServices);
void RegisterForAll(IEnumerable<Type> tServices);
void RegisterFor(Type tService, params Type[] tInterfaces);
void RegisterFor(Type tService, params IEnumerable<Type> tInterfaces);
}
我们需要的一切都在这里。实际的容器 IServicesContainer
和注册器 IServicesRegistrar
。一个容器应该同时实现 IServicesContainer
和 IServicesRegistrar
,但注册器仅在注册时需要,因此稍后我们可以只使用容器本身。
我们需要一个辅助类型来存储关于类型和实例的信息
internal class ServiceDescriptor
{
public Type ServiceType { get; set; }
public object Instance { get; set; }
}
让我们从容器开始
internal class ServicesContainer : IServicesRegistrar
{
private readonly IDictionary<Type, ServiceDescriptor> _services =
new Dictionary<Type, ServiceDescriptor>();
public TService Resolve<TService>()
{
return (TService)Resolve(typeof(TService));
}
public object Resolve(Type tService)
{
return GetInstance(tService);
}
...
}
一个字典将保存映射。GetInstance
将查找现有实例或创建一个新实例。让我们继续使用 GetInstance
internal class ServicesContainer : IServicesRegistrar
{
...
private object GetInstance(Type tService)
{
if (_services.ContainsKey(tService))
return GetInstance(_services[tService]);
var genericDefinition = tService.GetGenericTypeDefinition();
if (_services.ContainsKey(genericDefinition))
return GetGenericInstance(tService,
_services[genericDefinition].ServiceType);
throw new Exception("Type not registered" + tService);
}
...
}
有三种情况
- 类型已知,如果是这样,我们请求
GetInstance
,但这次使用ServiceDescriptor
作为参数,这个重载将是下一个。 - 类型未知,但它可能是一个泛型类型,并且它的泛型类型定义已知,如果是这样,我们要求
GetGenericInstance
来解决这个问题。 - 类型未知,你已经看过这部电影了,所以没必要告诉你接下来会发生什么。
internal class ServicesContainer : IServicesRegistrar
{
...
private object GetInstance(ServiceDescriptor serviceDescriptor)
{
return serviceDescriptor.Instance ?? ( serviceDescriptor.Instance =
CreateInstance(serviceDescriptor.ServiceType));
}
}
这里没什么,只是将一个接口解析为一个具体类,然后要求 CreateInstance
实例化该类。真正有趣的事情发生在 CreateInstance
中,这就是为什么它不得不等待。让我们先看看 GetGenericInstance
internal class ServicesContainer : IServicesRegistar
{
...
private object GetGenericInstance(Type tService, Type genericDefinition)
{
var genericArguments = tService.GetGenericArguments();
var actualType = genericDefinition.MakeGenericType(genericArguments);
var result = CreateInstance(actualType);
_services[tService] = new ServiceDescriptor
{
ServiceType = actualType,
Instance = result
};
return result;
}
}
泛型参数取自实际请求的类型 tService
,并且从已注册的类型和这些参数创建一个新的泛型类型。然后我们请求 CreateInstance
来帮助我们。我想是时候了
internal class ServicesContainer : IServicesRegistrar
{
...
private object CreateInstance(Type serviceType)
{
var ctor = serviceType.GetConstructors().First();
var dependecies = ctor.GetParameters()
.Select(p => Resolve(p.ParameterType)).ToArray();
return (IService)ctor.Invoke(dependecies);
}
}
首先我们获取一个构造函数,应该只有一个。然后我们获取构造函数参数类型(依赖项)并解析它们。最后,我们创建并返回实例。就这样,我们只需要注册类型,我们的 IoC 容器就准备好了。
internal class ServicesContainer : IServicesRegistrar
{
...
public ITypeRegistrar RegisterForAll(params Type[] implementations)
{
return Register((IEnumerable<Type>)implementations);
}
public ITypeRegistrar RegisterForAll(IEnumerable<Type> implementations)
{
foreach (var impl in implementations)
RegisterFor(impl, impl.GetInterfaces());
return this;
}
public ITypeRegistrar RegisterFor(Type implementation, params Type[] interfaces)
{
return RegisterFor(implementation, (IEnumerable<type>)interfaces);
}
public ITypeRegistrar RegisterFor
(Type implementation, IEnumerable<type> interfaces)
{
foreach (var @interface in interfaces)
_services[GetRegistrableType(@interface)] =
new ServiceDescriptor
{
ServiceType = implementation
};
return this;
}
private static Type GetRegistrableType(Type type)
{
return type.IsGenericType && type.ContainsGeneric ?
type.GetGenericTypeDefinition() : type;
}
}
正如你所看到的,有一些变化。ITypeRegistrar
只是 IServicesRegistrar
的一个基类,并且所有 void
成员都已替换为 ITypeRegistrar
以允许方法链。
泛型注册
注册泛型有两种情况,这些情况已通过方法 GetRegistrableType
解决。下一节将展示一个例子。让我们看看这些情况
让我们以最简单的方式看看这两种情况
public interface ITypeNamePrinter<TType>
{
void Print();
}
public class TypeNamePrinter<TType> : ITypeNamePrinter<TType>
{
public void Print()
{
Console.WriteLine(typeof(TType).FullName);
}
}
[TestFixture]
[TestClass]
public class GenericRegistrations
{
[Test]
[TestMethod]
public void ExplicitRegistration()
{
var services = new ServicesContainer();
services.RegisterForAll(typeof(TypeNamePrinter<int>),
typeof(TypeNamePrinter<string>), typeof(TypeNamePrinter<Type>));
services.Resolve<ITypeNamePrinter<int>>().Print();
services.Resolve<ITypeNamePrinter<string>>().Print();
services.Resolve<ITypeNamePrinter<Type>>().Print();
Assert.Throws(typeof(Exception), () =>
services.Resolve<ITypeNamePrinter<float>>().Print());
}
[Test]
[TestMethod]
public void ImplicitRegistration()
{
var services = new ServicesContainer();
services.RegisterForAll(typeof(TypeNamePrinter<>));
services.Resolve<ITypeNamePrinter<int>>().Print();
services.Resolve<ITypeNamePrinter<string>>().Print();
services.Resolve<ITypeNamePrinter<Type>>().Print();
Assert.DoesNotThrow(() =>
services.Resolve<ITypeNamePrinter<float>>().Print());
}
}
显式实现示例注册每个类型。当请求未显式注册的类型时,将抛出异常。隐式注册仅注册开放构造类型,并且每次您要求容器提供新封闭构造类型的实例时,它都会为该类型创建注册和实例。使用隐式和显式的组合可以产生一个非常灵活和有趣的情况。为了结束这个话题,让我们看一个更复杂一点的例子,使用显式注册。
public interface IExplicit<TType>
{
void Print();
}
public class StringExplicit : IExplicit<string>
{
public void Print()
{
Console.WriteLine("System.String");
}
}
public class IntExplicit : IExplicit<int>
{
public void Print()
{
Console.WriteLine("System.Int32");
}
}
public class TypeExplicit : IExplicit<Type>
{
public void Print()
{
Console.WriteLine("System.Type");
}
}
[TestFixture]
[TestClass]
public class ExplicitRegistrations
{
[Test]
[TestMethod]
public void Test()
{
var services = new ServicesContainer();
services.RegisterForAll(typeof(StringExplicit),
typeof(IntExplicit), typeof(TypeExplicit));
services.Resolve<IExplicit<int>>().Print();
services.Resolve<IExplicit<string>>().Print();
services.Resolve<IExplicit<Type>>().Print();
}
}
IoC 王国
我们的新 IoC 容器已经准备好了。让我们玩一下
public interface IKing : IService
{
IBoss<IGuard> Captain { get; }
IBoss<IMaid> Mistress { get; }
void RuleTheCastle();
}
public interface IServant : IService
{
void Serve();
}
public interface IGuard : IServant
{
}
public interface IMaid : IServant
{
}
public interface IBoss<TServant>
where TServant : IServant
{
TServant Servant { get; }
void OrderServantToServe();
}
public class King : Service, IKing
{
public King(IBoss<IGuard> captain, IBoss<IMaid> mistress)
{
Captain = captain;
Mistress = mistress;
}
public void RuleTheCastle()
{
Console.WriteLine("Rule!!!");
Captain.OrderServantToServe();
Mistress.OrderServantToServe();
}
public IBoss<IGuard> Captain
{
get;
private set;
}
public IBoss<IMaid> Mistress
{
get;
private set;
}
}
public class Boss<TServant> : Service, IBoss<TServant>
where TServant : IServant
{
public Boss(TServant servant)
{
Servant = servant;
}
public TServant Servant
{
get;
private set;
}
public void OrderServantToServe()
{
Servant.Serve();
}
}
public class Guard : Service, IGuard
{
public void Serve()
{
Console.WriteLine("Watch!!");
}
}
public class Maid : Service, IMaid
{
public void Serve()
{
Console.WriteLine("Clean!!");
}
}
解决方案中包含一个测试项目,它包含这个示例,它是一个虚拟的,但拥有我们测试所需的一切。还包括几个用于注册的类型,但它们太蹩脚了,无法在这里解释。
在告别之前,让我们看看如何使用容器。
var services = new ServicesContainer();
services.RegisterForAll(ServicesImplementation .FromAssemblyContaining<IKing>());
var king = services.Resolve<IKing>();
king.RuleTheCastle();
我希望它会有用!!!享受!!
参考资料