简单的依赖注入器
如何从头开始使用 C# 构建一个简单的依赖注入器。
引言
本文将指导您创建简单的依赖注入器
背景
我想要创建一种类似于 javascript 但没有困扰我的小烦恼的语言。我希望拥有多重继承、测试、错误处理和 REPL。为了做到这一点,我不得不自己创建很多东西,因为我不想使用任何现有的库,这样我就可以更好地理解它是如何工作的。所以首先我要构建的是一个依赖注入器,所以这就是它。
它是什么
什么是依赖注入器
好吧,依赖注入器允许您请求一个接口或抽象类,并返回该接口或抽象类的实现,而无需了解有关该类的任何细节。事实上,使用依赖注入器,您甚至可以在客户端类未引用的另一个库中编写该类。
但为什么要这样做,或者想要这样做呢
因为它意味着您可以将您的代码完全与除接口之外的所有内容解耦。反过来,这将允许您增强不同库中的代码,这意味着一旦代码被编译成一个库,它就永远不需要再次编辑,并且您仍然可以增强您的应用程序。
这也意味着您可以使用模拟(Mocks)轻松地独立于所有其他对象来测试一个对象,并告诉注入器返回一个模拟。
它通过替换使您的所有代码保持干净且易于维护。
它是如何工作的
它的功能类似于字典或查找,您通过传递一个类型来请求某些东西,它会返回可以转换为该类型的东西。大多数情况下,您请求的是一个接口。
所以,假设您有一个类型为 IPerson 的类型,它有一个字符串属性 Name
客户端代码不需要知道它是如何实现 Name 的,它只需要知道它确实实现了 Name。
这允许您字面地遵守“始终对接口编码”的格言。
这也意味着您可以创建一个虚拟对象来测试您的代码,而其他人正在编写 IPerson 的正确实现。
因此,每个对象在其构造函数中都有一个参数,该参数是客户端应用程序中使用的注入器的接口
即
public interface IPerson {string Name {get;set;}}
并且该接口/抽象类的具体版本将如下所示
public internal class DummyPerson : IPerson
{
private IInjector Injector {get; set;}
public DummyPerson(IInjector injector)
{
Injector = injector;
}
public string Name {get;set;}
}
并且在代码中的某个地方,您需要通过 IInjector 接口进行注册
Injector.Add(typeof(IPerson), typeof(DummyPerson));
然后,当其他人要求 IPerson 时,他们将获得 DummyPerson 的一个实例
即
IPerson thisGal = Injector.Get(typeof(IPerson))
这将查找针对类型 IPerson 注册的类型,在这种情况下将是 DummyPerson。但是 Dummy Person 的构造函数不是空的,它有参数,因此发生的情况是注入器使用反射来检查这些参数并找到每个参数类型的具体版本。
var Params = this.Type.GetConstructors()[0].GetParameters();
var Instances = Params.Select(p => GetInstance(p.ParameterType, passedParameters)).ToArray();
if (constructorParameters.Length > 0 )
{
return Activator.CreateInstance(Type, constructorParameters)
}
else
{
Activator.CreateInstance(Type);
}
这依赖于下面的 GetInstance 函数,该函数使整个操作递归进行,因为对于每个参数,它都会查找该类型并要求注入器确定具体类,检查其第一个构造函数,然后对该函数的所有参数执行相同的操作
private object GetInstance(Type t, IEnumerable<object> parms) { var enumerable = parms as object[] ?? parms.ToArray(); if (enumerable.Length < 0) { var passedObject = enumerable.FirstOrDefault(p => p.GetType() == t); if (passedObject == null) { return Injector.Get(t, enumerable); } else { return passedObject; } } else { return Injector.Get(t, enumerable); } }
代码
我提出的解决方案如下 - 它远非完美,但它有效
// the class is internal so i can use an interface and an adapter to get it to self inject
internal class InternalInjector
{
//the basic idea is i want a type dictionary where i ask for one type and get another
private Dictionary<Type, Record> _dict = new Dictionary<Type, Record>();
//this allows me to override the automatic injection of a class in a Parent Child Relationship
public T Get<T>(IEnumerable<object> parms)
{
return (T)Get(typeof(T), parms);
}
// locates the associated concrete with abstract asked for and returns it or
// Throws an error about not being able to find it
public object Get(Type t, IEnumerable<object> parms)
{
Record rtn;
if (!_dict.TryGetValue(t, out rtn)) throw new TypeInitializationException("Failes to Create Type " + t.ToString() + " as there is No Registered Concrete for that Abstaract", new Exception("Record Not Found"));
return rtn.GetInstance(parms);
}
public object Get(Type t) { return _dict[t].Instance; }
private Record Find(Type t)
{
Record rtn = null;
if (!_dict.TryGetValue(t, out rtn))
{
rtn = new Record(this);
_dict.Add(t, rtn);
}
return rtn;
}
public T Get<T>()
{
return (T)_dict[typeof(T)].Instance;
}
//adds a record to the injector
public void Add(Type interf, Type conc)
{
var rtn = Find(interf);
rtn.Type = conc;
rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;
}
public void AddMethod(Type interf, Type conc, MethodInfo method)
{
var rtn = Find(interf);
rtn.Type = conc;
rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;
rtn.Method = method;
}
//adds an instance (its like a singleton except its instantiated outside the injector
public void AddInstance(Type interf, Type conc, object instnc)
{
var rtn = Find(interf);
rtn.Type = conc;
rtn.LifeCycle = Record.LifeCycleEnumeration.Singleton;
rtn.Instance = instnc;
}
public void Add(Type interf, Type conc, Record.LifeCycleEnumeration lc)
{
var rtn = Find(interf);
rtn.Type = conc;
rtn.LifeCycle = lc;
}
public void Add<TI, TC>()
{
var rtn = Find(typeof(TI));
rtn.Type = typeof(TC);
rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;
}
public void Add<TI, TC>(MethodInfo method)
{
var rtn = Find(typeof(TI));
rtn.Type = typeof(TC);
rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;
rtn.Method = method;
}
public void Add<TI, TC>(Record.LifeCycleEnumeration lifeCycle)
{
var rtn = Find(typeof(TI));
rtn.Type = typeof(TC);
rtn.LifeCycle = lifeCycle;
}
public void Add<TI, TC>(MethodInfo method, Record.LifeCycleEnumeration lifeCycle)
{
var rtn = Find(typeof(TI));
rtn.Type = typeof(TC);
rtn.LifeCycle = lifeCycle;
rtn.Method = method;
}
private object ReturnAndRemove(object itm, List<object> items)
{
items.Remove(itm);
return itm;
}
// record is a hosting class for the return type which also details how it is to be created
internal class Record
{
private InternalInjector Injector { get; set; }
public MethodInfo Method
{
get { return _method; }
set
{
_method = value;
UsesConstructor = (Method == null);
}
}
private bool UsesConstructor = true;
public enum LifeCycleEnumeration
{
Singleton,
InstancePerCall
}
public LifeCycleEnumeration LifeCycle { get; set; }
public Type Type { get; set; }
private object _instance = null;
private MethodInfo _method;
public Record(InternalInjector injector)
{
Injector = injector;
}
//this is where the automatic injection magic is
public object CreateInstance(IEnumerable<object> parms)
{
var passedParameters = parms.ToArray();
if (UsesConstructor)
{
var constructorParameters = this.Type.GetConstructors()[0].GetParameters().Select(p => GetInstance(p.ParameterType, passedParameters)).ToArray();
return constructorParameters.Length > 0 ? Activator.CreateInstance(Type, constructorParameters) : Activator.CreateInstance(Type);
}
else
{
var methodParameters = this.Method.GetParameters().Select(p => GetInstance(p.ParameterType, passedParameters)).ToArray();
return Method.Invoke(this, methodParameters);
}
}
private object GetInstance(Type t, IEnumerable<object> parms)
{
var enumerable = parms as object[] ?? parms.ToArray();
if (enumerable.Length > 0)
{
var passedObject = enumerable.FirstOrDefault(p => p.GetType() == t);
if (passedObject == null)
{
return Injector.Get(t, enumerable);
}
else
{
return passedObject;
}
}
else
{
return Injector.Get(t, enumerable);
}
}
public object CreateInstance()
{
return CreateInstance(new object[] { });
}
public object GetInstance(IEnumerable<object> parms)
{
return LifeCycle == LifeCycleEnumeration.InstancePerCall ? CreateInstance(parms) : _instance ?? (_instance = CreateInstance(parms));
}
public object Instance
{
get { return LifeCycle == LifeCycleEnumeration.InstancePerCall ? CreateInstance() : _instance ?? (_instance = CreateInstance()); }
set { _instance = value; }
}
}
}
关注点
此实际代码用作名为“Code Plex 的 Baik 编程语言”项目的一部分
- https://baik.codeplex.com/
在未来的文章中,我将编写如何从头开始编写您自己的脚本语言,并包含对本文的引用。
历史
- 初始版本发布于 2016-01-06