手写实现简单版IOC

时间:2022-04-13 09:45:40

概述

IOC (Inversion of Control) 控制反转,大家应该都比较熟悉了、应该也都有用过,这里就不具体介绍了。自己平时也有用到过IOC,但是对它的具体实现原理只有一个模糊的概念,所以决定自己手动实现一个简单IOC。

开始

首先呢我们得知道IOC的主要作用是什么,才能开始动手写。IOC主要不就是负责创建对象以及管理生命周期嘛,那我们就开始动手啦。

比如现在有一个IAnimal接口Animal继承接口,然后就是个Call的方法。一般我们使用的时候都是IAnimal animal=new Animal(); 如果是使用第三方IOC容器实现的话,我们需要先注册一下类型才能获取到实例。

所以我们先来个最简单的仿照这个过程:

新建一个Container,然后里面有一个类型注册的方法ResgisterType和一个返回实例的方法Rerolve,还有一个存储类型的字典,具体代码如下

        private static Dictionary<string, object> ContainerTypeDictionary = new Dictionary<string, object>();/// <summary>
/// 注册类型
/// </summary>
/// <typeparam name="IT"></typeparam>
/// <typeparam name="T"></typeparam>
public void ResgisterType<IT,T>()
{
if (!ContainerTypeDictionary.ContainsKey(typeof(IT).FullName))
ContainerTypeDictionary.Add(typeof(IT).FullName, typeof(T));
} /// <summary>
/// 根据注册信息生成实例
/// </summary>
/// <typeparam name="IT"></typeparam>
/// <returns></returns>
public IT Rerolve<IT>()
{
string key = typeof(IT).FullName;
Type type = (Type)ContainerTypeDictionary[key];
       return (IT)Activator.CreateInstance(type);
     }

然后我们新建一个控制台测试一下

Container container = new Container();
container.ResgisterType<IAnimal, Animal>();
IAnimal animal= container.Rerolve<IAnimal>();

然后可以在不依赖具体对象Animal的情况下成功的创建一个animal实例。

之后我们就可以考虑复杂一点的情况了,现在我们的Animal类里没有做任何事,假如它的构造函数里依赖于另一个对象呢,这样我们的程序肯定是会报错的。比如下面这样:

public class Animal: IAnimal
{
public Animal(Dog dog)
{ }
}

我们容器目前能创建的对象实例,只有通过ResgisterType方法注册过类型的,而像Animal里依赖的不能实现创建,所以这个时候就需要用到依赖注入了。

关于依赖注入与控制反转的关系,我个人的理解是:控制反转是一种设计思想,而依赖注入则是实现控制反转思想的方法。

IOC容器一般依赖注入有三种:构造函数注入、方法注入、属性注入。

那么我们就来照瓢画葫芦,实现一下构造函数注入。一般IOC容器构造函数注入是通过一个特性来识别注入的,如果没有标记特性则去找构造函数参数个数最多的,我们就按照这个思路来。

首先我们新建一个LInjectionConstructorAttribute类,只需继承Attribute就行了。

public class LInjectionConstructorAttribute :Attribute
{
}

然后在刚才那个Animal构造函数上标记上特性,接下来就开始写代码。

/// <summary>
/// 根据注册信息生成实例
/// </summary>
/// <typeparam name="IT"></typeparam>
/// <returns></returns>
public IT Rerolve<IT>()
{
string key = typeof(IT).FullName;
Type type = (Type)ContainerTypeDictionary[key]; return (IT)CreateType(type);
}
/// <summary>
/// 根据提供的类型创建类型实例并返回
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private object CreateType(Type type)
{
var ctorArray = type.GetConstructors();
if (ctorArray.Count(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true)) > )
{
//获取带特性标记的构造函数参数
foreach (var cotr in type.GetConstructors().Where(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true)))
{
var paraArray = cotr.GetParameters();//获取参数数组
if (paraArray.Length == )
{
return Activator.CreateInstance(type);
} List<object> listPara = new List<object>();
foreach (var para in paraArray)
{
string paraKey = para.ParameterType.FullName;//参数类型名称
//从字典中取出缓存的目标对象并创建对象
Type paraTargetType = (Type)ContainerTypeDictionary[paraKey];
object oPara = CreateType(paraTargetType);//递归
listPara.Add(oPara);
}
return Activator.CreateInstance(type,listPara.ToArray());
} return Activator.CreateInstance(type);
}
else
{
//没有标记特性则使用参数最多的构造函数
var ctor = ctorArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
var paraArray = ctor.GetParameters();//获取参数数组
if (paraArray.Length == )
{
return Activator.CreateInstance(type);
} List<object> listPara = new List<object>();
foreach (var para in paraArray)
{
string paraKey = para.ParameterType.FullName;//参数类型名称
//从字典中取出缓存的目标对象并创建对象
Type paraTargetType = (Type)ContainerTypeDictionary[paraKey];
object oPara = CreateType(paraTargetType);//递归
listPara.Add(oPara);
}
return Activator.CreateInstance(type, listPara.ToArray());
}
}

这里说下为什么用到递归,在我们项目中使用会有层层依赖的关系。比如我这里Animal依赖于Dog只有一层依赖,如果Gog又依赖于猫、猫依赖于鱼。。。(当然这里只是打个比方)

因为我们不知道具体有几层依赖,所以使用了递归的方法,直到将所有依赖的对象得到后再创建实例。

然后我们再来测试

Container container = new Container();
container.ResgisterType<IAnimal, Animal>();
container.ResgisterType<IDog, Dog>();
IAnimal animal= container.Rerolve<IAnimal>();

注意,如果测试标记特性的一定不要忘了在构造函数上标记特性,然后我们会发现最终也可以得到animal对象。

然后,创建对象这一块我们先告一段落。接下来进行生命周期管理。

一般的IOC容器都支持三种类型:Transient每次都得到一个新的对象、Scoped同一个域(或者请求、线程)中使用同一个对象、Singleton整个程序生命周期都使用同一实例对象。

那按照我们以上的代码怎么才能实现生命周期管理呢?我是这么想的:既然创建对象的工作都是由我容器来做了,那么我们在创建完对象之后能不能像注册类型一样将对象保存起来呢?

所以我这里使用了简单的字典来存储对象实例,然后通过判断使用的哪一种生命周期来返回新的对象或是直接返回字典里的对象。直接改造上面的代码了:

/// <summary>
/// 根据提供的类型创建类型实例并返回
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private object CreateType(Type type)
{
var ctorArray = type.GetConstructors();
if (ctorArray.Count(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true)) > )
{
//获取带特性标记的构造函数参数
foreach (var cotr in type.GetConstructors().Where(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true)))
{
var paraArray = cotr.GetParameters();//获取参数数组
if (paraArray.Length == )
{
//return Activator.CreateInstance(type);
return GetSocpe(type);
} List<object> listPara = new List<object>();
foreach (var para in paraArray)
{
string paraKey = para.ParameterType.FullName;//参数类型名称
//从字典中取出缓存的目标对象并创建对象
Type paraTargetType = (Type)ContainerTypeDictionary[paraKey];
object oPara = CreateType(paraTargetType);//递归
listPara.Add(oPara);
}
//return Activator.CreateInstance(type,listPara.ToArray());
return GetSocpe(type, listPara.ToArray());
} return GetSocpe(type);
//return Activator.CreateInstance(type);
}
else
{
//没有标记特性则使用参数最多的构造函数
var ctor = ctorArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
var paraArray = ctor.GetParameters();//获取参数数组
if (paraArray.Length == )
{
//return Activator.CreateInstance(type);
return GetSocpe(type);
} List<object> listPara = new List<object>();
foreach (var para in paraArray)
{
string paraKey = para.ParameterType.FullName;//参数类型名称
//从字典中取出缓存的目标对象并创建对象
Type paraTargetType = (Type)ContainerTypeDictionary[paraKey];
object oPara = CreateType(paraTargetType);//递归
listPara.Add(oPara);
}
return GetSocpe(type, listPara.ToArray());
//return Activator.CreateInstance(type, listPara.ToArray());
}
}
private object GetSocpe(Type type, params object[] listPara)
{
if (_scopeType == (int)Scope.Singleton)
{
return GetTypeSingleton(type, listPara);
}
else if (_scopeType == (int)Scope.Transient)
{
return GetTypeTransient(type, listPara);
}
else
{
return GetTypeScoped(type, listPara);
}
} #region 生命周期
/// <summary>
/// 设置获取实例对象生命周期为Singleton
/// </summary>
/// <param name="type"></param>
/// <param name="listPara"></param>
/// <returns></returns>
private object GetTypeSingleton(Type type, params object[] listPara)
{
if (ContainerExampleDictionary.ContainsKey(type.FullName))
{
lock (locker)
{
if (ContainerExampleDictionary.ContainsKey(type.FullName))
{
return ContainerExampleDictionary[type.FullName];
}
}
} if (listPara.Length == )
{
var Example = Activator.CreateInstance(type);
ContainerExampleDictionary.Add(type.FullName, Example);
return Example;
}
else
{
var Example = Activator.CreateInstance(type, listPara.ToArray());
ContainerExampleDictionary.Add(type.FullName, Example);
return Example;
}
} /// <summary>
/// 设置获取实例对象生命周期为Transient
/// </summary>
/// <param name="type"></param>
/// <param name="listPara"></param>
/// <returns></returns>
private object GetTypeTransient(Type type, params object[] listPara)
{
if (listPara.Length == )
{
var Example = Activator.CreateInstance(type);
//ContainerExampleDictionary.Add(type.FullName, Example);
return Example;
}
else
{
var Example = Activator.CreateInstance(type, listPara.ToArray());
//ContainerExampleDictionary.Add(type.FullName, Example);
return Example;
}
} /// <summary>
/// 设置获取实例对象生命周期为Scoped
/// </summary>
/// <param name="type"></param>
/// <param name="listPara"></param>
/// <returns></returns>
private object GetTypeScoped(Type type, params object[] listPara)
{
var pid = System.Threading.Thread.CurrentThread.ManagedThreadId;
if (ContainerExampleDictionary.ContainsKey(type.FullName + pid))
{
lock (locker)
{
if (ContainerExampleDictionary.ContainsKey(type.FullName + pid))
{
return ContainerExampleDictionary[type.FullName + pid];
}
}
} if (listPara.Length == )
{
var Example = Activator.CreateInstance(type);
ContainerExampleDictionary.Add(type.FullName + pid, Example);
return Example;
}
else
{
var Example = Activator.CreateInstance(type, listPara.ToArray());
ContainerExampleDictionary.Add(type.FullName + pid, Example);
return Example;
}
}
#endregion
private static Dictionary<string, object> ContainerExampleDictionary = new Dictionary<string, object>();
private static int _scopeType;
private static readonly object locker = new object();
public int scopeType
{
get
{
return _scopeType;
}
set
{
_scopeType = value;
}
}
public enum Scope
{
Singleton = ,
Transient = ,
Scoped =
}

然后调用的时候先声明下要使用的声明周期类型就行啦

Container container = new Container();
container.scopeType = (int)Container.Scope.Singleton;
container.ResgisterType<IAnimal, Animal>();
container.ResgisterType<IDog, Dog>();
IAnimal animal= container.Rerolve<IAnimal>();

说下三种生命周期管理的实现:

Transient:则可以直接创建一个实例

Scoped:使用的是同一个线程内使用同一个对象实例,使用var pid = System.Threading.Thread.CurrentThread.ManagedThreadId;获取线程id来判断的

Singleton:这种则只需一个单例模式获取就好了

到这里就先告一段落了,以上只是一个简单实现,代码还有需改进的地方以及可以扩展的功能,欢迎提意见指出错误。同时代码已上传GigHub,还有不懂的可以参考下代码。

源码地址:https://github.com/liangchengxuyuan/IocContainer