这两天我用Unity写游戏,特别需要一个IoC(控制反转)容器来简化我的代码逻辑,达到业务之间解耦的效果。
我尝试了许多包括.NET自带的依赖注入(DI)容器,但使用起来都不太符合我的代码习惯。
比如说,如果我有接口类IMyService和实现类MyService,想要通过.NET的IoC依赖注入容器,我就得写一个这样的配置类来绑定服务和接口:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyService, MyService>();
}
这样写是不是很蠢?如果你写过Springboot,你应该知道,这种情况下在实现类类名上方打一个@Service注解就可以解决问题了。
说巧不巧,C#也有一个叫特性的东西,跟Java的注解几乎是一码事。因此我决定自行编写IoC容器,对Springboot进行拙劣但不失乐趣的模仿。
我们先针对非Unity游戏进行操作。
我创建了一个解决方案,在解决方案下方建立了一个控制台项目ConsoleApp1和一个类库项目EasyInject,分别用来进行依赖注入测试和编写IoC容器。
在EasyInject/Attributes下,我建立了两个特性类,分别叫AutowiredAttribute和ComponentAttribute,前者用于打在成员变量字段前进行字段注入,后者用于标记这个类应该被视作类似于Bean的东西记录在IoC容器当中。代码如下:
namespace EasyInject.Attributes;
[AttributeUsage(AttributeTargets.Field)]
public class AutowiredAttribute : Attribute
{
}
namespace EasyInject.Attributes;
[AttributeUsage(AttributeTargets.Class)]
public class ComponentAttribute : Attribute
{
}
现在我们就可以编写IoC容器了。
先把代码放上来,然后我们一行行讲我的思路:
using System.Reflection;
using EasyInject.Attributes;
namespace EasyInject;
public class MyIoC
{
private readonly Dictionary<Type, object> _services = new();
public MyIoC()
{
// 扫描所有程序集中打了Component特性的类
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => t.GetCustomAttributes(typeof(ComponentAttribute), true).Length > 0).ToList();
// 先进行实例化(包括构造函数的依赖注入)
while (types.Count > 0)
{
for (var i = 0; i < types.Count; i++)
{
var type = types[i];
// 获取构造函数
var constructors = type.GetConstructors();
// 实例
object? instance = null;
// 遍历构造函数,找到可以实例化的构造函数
foreach (var constructor in constructors)
{
// 获取构造函数的参数
var parameters = constructor.GetParameters();
// 构造函数的参数实例
var parameterInstances = new object[parameters.Length];
for (var j = 0; j < parameters.Length; j++)
{
var parameterType = parameters[j].Parameter