依赖注入:一个Mini版的依赖注入框架
前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍。为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类似的原理创建了一个简易版本的依赖注入框架,也就是我们在前面多次提及的Cat。
源代码下载
普通服务的注册与消费
泛型服务的注册与消费
多服务实例的提供
服务实例的生命周期
一、编程体验
虽然我们对这个名为Cat的依赖注入框架进行了最大限度的简化,但是与.NET Core框架内部使用的真实依赖注入框架相比,Cat不仅采用了一致的设计,而且几乎具备了后者所有的功能特性。为了让大家对Cat具有一个感官的认识,我们先来演示一下如何利用它来提供我们所需的服务实例。
作为依赖注入容器的Cat对象不仅仅作为服务实例的提供者,它同时还需要维护着服务实例的生命周期。Cat提供了三种生命周期模式,如果要了解它们之间的差异,就必须对多个Cat之间的层次关系有充分的认识。一个代表依赖注入容器的Cat对象用来创建其他的Cat对象,后者视前者为“父容器”,所以多个Cat对象通过其“父子关系”维系一个树形层次化结构。不过这仅仅是一个逻辑结构而已,实际上每个Cat对象只会按照下图所示的方式引用整棵树的根。
在了解了多个Cat对象之间的关系之后,对于三种预定义的生命周期模式就很好理解了。如下所示的Lifetime枚举代表着三种生命周期模式,其中Transient代表容器针对每次服务请求都会创建一个新的服务实例,而Self则是将提供服务实例保存在当前容器中,它代表针对某个容器范围内的单例模式,Root则是将每个容器提供的服务实例统一存放到根容器中,所以该模式能够在多个“同根”容器范围内确保提供的服务是单例的。
public enum Lifetime{ Root, Self, Transient}
代表依赖注入容器的Cat对象之所以能够为我们提供所需服务实例,其根本前提是相应的服务注册在此之前已经添加到容器之中。服务总是针对服务类型(接口、抽象类或者具体类型)进行注册,Cat通过定义的扩展方法提供了如下三种注册方式。除了直接提供服务实例的形式外(默认采用Root模式),我们在注册服务的时候必须指定一个具体的生命周期模式。
指定具体的实现类型。
提供一个服务实例。
指定一个创建服务实例的工厂。
我们定义了如下的接口和对应的实现类型来演示针对Cat的服务注册。其中Foo、Bar、Baz和Gux分别实现了对应的接口IFoo、IBar、IBaz和IGux,其中Gux类型上标注了一个MapToAttribute特性注册了与对应接口IGux之间的映射。为了反映Cat对服务实例生命周期的控制,我们让它们派生于同一个基类Base。Base实现了IDisposable接口,我们在其构造函数和实现的Dispose方法中输出相应的文本以确定对应的实例何时被创建和释放。我们还定义了一个泛型的接口IFoobar<T1, T2>和对应的实现类Foobar<T1, T2>来演示Cat针对泛型服务实例的提供。
public interface IFoo {}public interface IBar {}public interface IBaz {} public interface IGux {}public interface IFoobar<T1, T2> {}public class Base : IDisposable{public Base() => Console.WriteLine($"Instance of {GetType().Name} is created.");public void Dispose() => Console.WriteLine($"Instance of {GetType().Name} is disposed.");} public class Foo : Base, IFoo{ }public class Bar : Base, IBar{ }public class Baz : Base, IBaz{ } [MapTo(typeof(IGux), Lifetime.Root)]public class Gux : Base, IGux { }public class Foobar<T1, T2>: IFoobar<T1,T2>{public IFoo Foo { get; }public IBar Bar { get; }public Foobar(IFoo foo, IBar bar) { Foo = foo; Bar = bar; }}
在如下所示的代码片段中我们创建了一个Cat对象并采用上面提到的方式针对接口IFoo、IBar和IBaz注册了对应的服务,它们采用的生命周期模式分别为Transient、Self和Root。然后我们还调用了另一个将当前入口程序集作为参数的Register方法,该方法会解析指定程序集中标注了MapToAttribute特性的类型并作相应的服务注册,对于我们演示的程序来,该方法会完成针对IGux/Gux类型的服务注册。接下来我们利用Cat对象创建了它的两个子容器,并调用子容器的GetService<T>方法提供相应的服务实例。
class Program{static void Main() {var root = new Cat() .Register<IFoo, Foo>(Lifetime.Transient) .Register<IBar>(_=> new Bar(), Lifetime.Self) .Register<IBaz, Baz>(Lifetime.Root) .Register(Assembly.GetEntryAssembly());var cat1 = root.CreateChild();var cat2 = root.CreateChild(); void GetServices<TService>(Cat cat) { cat.GetService<TService>(); cat.GetService<TService>(); } GetServices<IFoo>(cat1); GetServices<IBar>(cat1); GetServices<IBaz>(cat1); GetServices<IGux>(cat1); Console.WriteLine(); GetServices<IFoo>(cat2); GetServices<IBar>(cat2); GetServices<IBaz>(cat2); GetServices<IGux>(cat2); }}
上面的程序运行之后会在控制台上输出如图3-7所示的结果,输出的内容不仅表明Cat能够根据添加的服务注册提供对应类型的服务实例,还体现了它对生命周期的控制。由于服务IFoo被注册为Transient服务,所以Cat针对该接口的服务提供四次请求都会创建一个全新的Foo对象。IBar服务的生命周期模式为Self,如果我们利用同一个Cat对象来提供对应的服务实例,该Cat对象只会创建一个Bar对象,所以整个过程中会创建两个Bar对象。IBaz和IGux服务采用Root生命周期,所以具有同根的两个Cat对象提供的总是同一个Baz/Gux对象,后者只会被创建一次。
除了提供类似于IFoo、IBar和IBaz这样非泛型的服务实例之外,如果具有针对泛型定义(Generic Definition)的服务注册,Cat同样也能提供泛型服务实例。如下面的代码片段所示,在为创建的Cat对象添加了针对IFoo和IBar接口的服务注册之后,我们调用Register方法注册了针对泛型定义IFoobar<,>的服务注册,具体的实现类型为Foobar<,>。当我们利用Cat对象提供一个类型为IFoobar<IFoo, IBar>的服务实例的时候,它会创建并返回一个Foobar<Foo, Bar>对象。
public class Program{public static void Main() {var cat = new Cat() .Register<IFoo, Foo>(Lifetime.Transient) .Register<IBar, Bar>(Lifetime.Transient) .Register(typeof(IFoobar<,>), typeof(Foobar<,>), Lifetime.Transient); var foobar = (Foobar<IFoo, IBar>)cat.GetService<IFoobar<IFoo, IBar>>(); Debug.Assert(foobar.Foo is Foo); Debug.Assert(foobar.Bar is Bar); }}
当我们在进行服务注册的时候,可以为同一个类型添加多个服务注册。虽然添加的所有服务注册均是有效的,不过由于扩展方法GetService<TService>总是返回一个唯一的服务实例,我们对该方法采用了“后来居上”的策略,即总是采用最近添加的服务注册来创建服务实例。如果我们调用另一个扩展方法GetServices<TService>,它将利用返回根据所有服务注册提供的服务实例。
如下面的代码片段所示,我们为创建的Cat对象添加了三个针对Base类型的服务注册,对应的实现类型分别为Foo、Bar和Baz。我们最后将Base作为泛型参数调用了GetServices<Base>方法,该方法会返回包含三个Base对象的集合,集合元素的类型分别为Foo、Bar和Baz。
public class Program{public static void Main() {var services = new Cat() .Register<Base, Foo>(Lifetime.Transient) .Register<Base, Bar>(Lifetime.Transient) .Register<Base, Baz>(Lifetime.Transient) .GetServices<Base>(); Debug.Assert(services.OfType<Foo>().Any()); Debug.Assert(services.OfType<Bar>().Any()); Debug.Assert(services.OfType<Baz>().Any()); }}
如果提供的服务实例实现了IDisposable接口,我们应该在适当的时候调用其Dispose方法释放该服务实例。由于服务实例的生命周期完全由作为依赖注入容器的Cat对象来管理,那么通过调用Dispose方法来释放服务实例自然也应该由它来负责。Cat针对提供服务实例的释放策略取决于采用的生命周期模式,具体的策略如下:
Transient和Self:所有实现了IDisposable接口的服务实例会被当前Cat对象保存起来,当Cat对象自身的Dispose方法被调用的时候,这些服务实例的Dispose方法会随之被调用。
Root:由于服务实例保存在作为根容器的Cat对象上,所以当这个Cat对象的Dispose方法被调用的时候,这些服务实例的Dispose方法会随之被调用。
上述的释放策略可以通过如下的演示实例来印证。我们在如下的代码片段中创建了一个Cat对象,并添加了相应的服务注册。我们接下来调用了CreateChild方法创建代表子容器的Cat对象,并用它提供了四个注册服务对应的实例。
class Program{static void Main() {using (var root = new Cat() .Register<IFoo, Foo>(Lifetime.Transient) .Register<IBar>(_ => new Bar(), Lifetime.Self) .Register<IBaz, Baz>(Lifetime.Root) .Register(Assembly.GetEntryAssembly())) {using (var cat = root.CreateChild()) { cat.GetService<IFoo>(); cat.GetService<IBar>(); cat.GetService<IBaz>(); cat.GetService<IGux>(); Console.WriteLine("Child cat is disposed."); } Console.WriteLine("Root cat is disposed."); } }}
由于两个Cat对象的创建都是在using块中进行的,所以它们的Dispose方法都会在using块结束的地方被调用。为了确定方法被调用的时机,我们特意在控制台上打印了相应的文字。该程序运行之后会在控制台上输出如下图所示的结果,我们可以看到当作为子容器的Cat对象的Dispose方法被调用的时候,由它提供的两个生命周期模式分别为Transient和Self的两个服务实例(Foo和Bar)被正常释放了。至于生命周期模式为Root的服务实例Baz和Gux,它的Dispose方法会延迟到作为根容器的Cat对象的Dispose方法被调用的时候。
二、设计与实现
在完成针对Cat的编程体验之后,我们来聊聊这个依赖注入容器的设计原理和具体实现。由于作为依赖注入容器的Cat对象总是利用预先添加的服务注册来提供对应的服务实例,所以服务注册至关重要。如下所示的就是表示服务注册的ServiceRegistry的定义,它具有三个核心属性(ServiceType、Lifetime和Factory),分别代表服务类型、生命周期模式和用来创建服务实例的工厂。最终用来创建服务实例的工厂体现为一个类型为Func<Cat,Type[], object>的委托对象,它的两个输入分别代表当前使用的Cat对象以及提供服务类型的泛型参数,如果提供的服务类型并不是一个泛型类型,这个参数被会指定为一个空的数组。
public class ServiceRegistry{public Type ServiceType { get; }public Lifetime Lifetime { get; }public Func<Cat,Type[], object> actory { get; }internal ServiceRegistry Next { get; set; } public ServiceRegistry(Type serviceType, Lifetime lifetime, Func<Cat,Type[], object> factory) { ServiceType = serviceType; Lifetime = lifetime; Factory = factory; } internal IEnumerable<ServiceRegistry> AsEnumerable() {var list = new List<ServiceRegistry>();for (var self = this; self!=null; self= self.Next) { list.Add(self); }return list; }}
我们将针对同一个服务类型(ServiceType属性相同)的多个ServiceRegistry组成一个链表,作为相邻节点的两个ServiceRegistry对象通过Next属性关联起来。我们为ServiceRegistry定义了一个AsEnumerable方法使它返回由当前以及后续节点组成的ServiceRegistry集合。如果当前ServiceRegistry为链表头,那么这个方法会返回链表上的所有ServiceRegistry对象。下图体现了服务注册核心三要素和链表结构。
在了解了表示服务注册的ServiceRegistry之后,我们来着重介绍表示依赖注入容器的Cat类型。如下面的代码片段所示,Cat同时实现了IServiceProvider和IDisposable接口,定义在前者中的GetService方法用于提供服务实例。作为根容器的Cat对象通过公共构造函数创建,另一个内部构造函数则用来创建作为子容器的Cat对象,指定的Cat对象将作为父容器。
public class Cat : IServiceProvider, IDisposable{internal readonly Cat _root;internal readonly ConcurrentDictionary<Type, ServiceRegistry> _registries;private readonly ConcurrentDictionary<Key, object> _services;private readonly ConcurrentBag<IDisposable> _disposables;private volatile bool _disposed; public Cat() { _registries = new ConcurrentDictionary<Type, ServiceRegistry>(); _root = this; _services = new ConcurrentDictionary<Key, object>(); _disposables = new ConcurrentBag<IDisposable>(); } internal Cat(Cat parent) { _root = parent._root; _registries = _root._registries; _services = new ConcurrentDictionary<Key, object>(); _disposables = new ConcurrentBag<IDisposable>(); } private void EnsureNotDisposed() {if (_disposed) {throw new ObjectDisposedException("Cat"); } } ...}
作为根容器的Cat对象通过_root字段表示。_registries字段返回的ConcurrentDictionary<Type, ServiceRegistry>对象用来存储所有添加的服务注册,该字典对象的Key和Value分别表示服务类型和ServiceRegistry链表,下图体现这一映射关系。由于需要负责完成对提供服务实例的释放工作,所以我们需要将实现了IDisposable接口的服务实例保存在通过_disposables字段表示的集合中。
由当前Cat对象提供的非Transient服务实例保存在由_services字段表示的一个ConcurrentDictionary<Key, object>对象上,该字典对象的键类型为如下所示的Key,它相当于是创建服务实例所使用的ServiceRegistry对象和泛型参数类型数组的组合。
internal class Key : IEquatable<Key>{public ServiceRegistry Registry { get; }public Type[] GenericArguments { get; } public Key(ServiceRegistry registry, Type[] genericArguments) { Registry = registry; GenericArguments = genericArguments; } public bool Equals(Key other) {if (Registry != other.Registry) {return false; }if (GenericArguments.Length != other.GenericArguments.Length) {return false; }for (int index = 0; index < GenericArguments.Length; index++) {if (GenericArguments[index] != other.GenericArguments[index]) {return false; } }return true; } public override int GetHashCode() {var hashCode = Registry.GetHashCode();for (int index = 0; index < GenericArguments.Length; index++) { hashCode ^= GenericArguments[index].GetHashCode(); }return hashCode; }public override bool Equals(object obj) => obj is Key key ? Equals(key) : false;}
虽然我们为Cat定义了若干扩展方法来提供多种不同的服务注册,但是这些方法最终都会调用如下这个Register方法,该方法会将提供的ServiceRegistry添加到_registries字段表示的字典对象中。值得注意的是,不论我们是调用哪个Cat对象的Register方法,指定的ServiceRegistry都会被添加到作为根容器的Cat对象上。
public class Cat : IServiceProvider, IDisposable{public Cat Register(ServiceRegistry registry) { EnsureNotDisposed();if (_registries.TryGetValue(registry.ServiceType, out var existing)) { _registries[registry.ServiceType] = registry; registry.Next = existing; }else { _registries[registry.ServiceType] = registry; }return this; } ...}
用来提供服务实例的核心操作实现在如下这个GetServiceCore方法中。如下面的代码片段所示,我们在调用该方法的时候需要指定对应的ServiceRegistry对象的服务类型的泛型参数。当该方法被执行的时候,对于Transient的生命周期模式,它会直接利用ServiceRegistry提供的工厂来创建服务实例。如果服务实例的类型实现了IDisposable接口,该对象会被添加到_disposables字段表示的待释放服务实例列表中。对于Root和Self生命周期模式,该方法会先根据提供的ServiceRegistry判断是否对应的服务实例已经存在,存在的服务实例会直接返回。
public class Cat : IServiceProvider, IDisposable{private object GetServiceCore(ServiceRegistry registry, Type[] genericArguments) {var key = new Key(registry, genericArguments);var serviceType = registry.ServiceType; switch (registry.Lifetime) {case Lifetime.Root: return GetOrCreate(_root._services, _root._disposables);case Lifetime.Self: return GetOrCreate(_services, _disposables);default: {var service = registry.Factory(this, genericArguments);if (service is IDisposable disposable && disposable != this) { _disposables.Add(disposable); }return service; } } object GetOrCreate(ConcurrentDictionary<Key, object> services, ConcurrentBag<IDisposable> disposables) {if (services.TryGetValue(key, out var service)) {return service; } service = registry.Factory(this, genericArguments); services[key] = service;if (service is IDisposable disposable) { disposables.Add(disposable); }return service; } }}
GetServiceCore方法只有在指定ServiceRegistry对应的服务实例不存在的情况下才会利用提供的工厂来创建服务实例,创建的服务实例会根据生命周期模式保存到作为根容器的Cat对象或者当前Cat对象上。如果提供的服务实例实现了IDisposable接口,在采用Root生命周期模式下会被保存到作为根容器的Cat对象的待释放列表中。如果生命周期模式为Self,它会被添加到当前Cat对象的待释放列表中。
在实现的GetService方法中,Cat会根据指定的服务类型找到对应的ServiceRegistry对象,并最终调用GetServiceCore方法来提供对应的服务实例。GetService方法还会解决一些特殊服务的提供问题,比如若服务类型为Cat或者IServiceProvider,该方法返回的就是它自己。如果服务类型为IEnumerable<T>,GetService方法会根据泛型参数类型T找到所有的ServiceRegistry并利用它们来创建对应的服务实例,最终返回的是由这些服务实例组成的集合。除了这些,针对泛型服务实例的提供也是在这个方法中解决的。
public class Cat : IServiceProvider, IDisposable{public object GetService(Type serviceType) { EnsureNotDisposed(); if (serviceType == typeof(Cat) || serviceType == typeof(IServiceProvider)) {return this; } ServiceRegistry registry;//IEnumerable<T>if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) {var elementType = serviceType.GetGenericArguments()[0];if (!_registries.TryGetValue(elementType, out registry)) {return Array.CreateInstance(elementType, 0); } var registries = registry.AsEnumerable();var services = registries.Select(it => GetServiceCore(it, Type.EmptyTypes)).ToArray(); Array array = Array.CreateInstance(elementType, services.Length); services.CopyTo(array, 0);return array; } //Genericif (serviceType.IsGenericType && !_registries.ContainsKey(serviceType)) {var definition = serviceType.GetGenericTypeDefinition();return _registries.TryGetValue(definition, out registry)? GetServiceCore(registry, serviceType.GetGenericArguments()) : null; } //Normalreturn _registries.TryGetValue(serviceType, out registry)? GetServiceCore(registry, new Type[0]) : null; } ...}
在实现的Dispose方法中,由于所有待释放的服务实例已经保存到_disposables字段表示的集合中,所以我们只需要依次调用它们的Dispose方法即可。在释放了所有服务实例并清空待释放列表后,Dispose还会清空_services字段表示的服务实例列表。
public class Cat : IServiceProvider, IDisposable{public void Dispose() { _disposed = true;foreach(var disposable in _disposables) { disposable.Dispose(); } _disposables.Clear(); _services.Clear(); } ...}
三、扩展方法
为了方便注册服务,我们定义了如下六个Register扩展方法。由于服务注册的添加总是需要调用Cat自身的Register方法来完成,所以这些方法最终都需要创建一个代表服务注册的ServiceRegistry对象。对于一个ServiceRegistry对象来说,它最为核心的元素莫过于表示服务实例创建工厂的Func<Cat,Type[], object>对象,所以上述这六个扩展方法需要解决的就是创建这么一个委托对象。
public static class CatExtensions{public static Cat Register(this Cat cat, Type from, Type to, Lifetime lifetime) { Func<Cat, Type[], object> factory = (_, arguments) => Create(_, to, arguments); cat.Register(new ServiceRegistry(from, lifetime, factory));return cat; } public static Cat Register<TFrom, TTo>(this Cat cat, Lifetime lifetime) where TTo:TFrom=> cat. Register(typeof(TFrom), typeof(TTo), lifetime); public static Cat Register(this Cat cat, Type serviceType, object instance) { Func<Cat, Type[], object> factory = (_, arguments) => instance; cat.Register(new ServiceRegistry(serviceType, Lifetime.Root, factory));return cat; } public static Cat Register<TService>(this Cat cat, TService instance) { Func<Cat, Type[], object> factory = (_, arguments) => instance; cat.Register(new ServiceRegistry(typeof(TService), Lifetime.Root, factory));return cat; } public static Cat Register(this Cat cat, Type serviceType, Func<Cat, object> factory, Lifetime lifetime) { cat.Register(new ServiceRegistry(serviceType, lifetime, (_, arguments) => factory(_)));return cat; } public static Cat Register<TService>(this Cat cat, Func<Cat,TService> factory, Lifetime lifetime) { cat.Register(new ServiceRegistry(typeof(TService), lifetime, (_,arguments)=>factory(_)));return cat; } private static object Create(Cat cat, Type type, Type[] genericArguments) {if (genericArguments.Length > 0) { type = type.MakeGenericType(genericArguments); }var constructors = type.GetConstructors();if (constructors.Length == 0) {throw new InvalidOperationException($"Cannot create the instance of {type} which does not have a public constructor."); }var constructor = constructors.FirstOrDefault(it => it.GetCustomAttributes(false).OfType<InjectionAttribute>().Any()); constructor ??= constructors.First();var parameters = constructor.GetParameters();if (parameters.Length == 0) {return Activator.CreateInstance(type); }var arguments = new object[parameters.Length];for (int index = 0; index < arguments.Length; index++) { arguments[index] = cat.GetService(parameters[index].ParameterType); }return constructor.Invoke(arguments); }}
由于前两个重载指定的是服务实现类型,所以我们需要调用对应的构造函数来创建服务实例,这一逻辑实现在私有的Create方法中。第三个扩展方法指定的直接就是服务实例,所以我们很容易将提供的参数转换成一个Func<Cat,Type[], object>。
我们刻意简化了构造函数的筛选逻辑。为了解决构造函数的选择问题,我们引入如下这个InjectionAttribute特性。我们将所有公共实例构造函数作为候选的构造函数,并会优先选择标注了该特性的构造函数。当构造函数被选择出来后,我们需要通过分析其参数类型并利用Cat对象来提供具体的参数值,这实际上是一个递归的过程。最终我们将针对构造函数的调用转换成Func<Cat,Type[], object>对象,进而创建出表示服务注册的ServiceRegistry对象。
[AttributeUsage( AttributeTargets.Constructor)]public class InjectionAttribute: Attribute {}
前面给出的代码片段还提供了HasRegistry和HasRegistry<T>方法来确定指定类型的服务注册是否存在。除此之外,用于提供服务实例的泛型方法GetService<T>和用于提供所有指定类型服务实例的GetServices<TService>方法采用了如下的定义方式。
public static class CatExtensions{public static IEnumerable<TService> GetServices<T>(this Cat cat) => cat.GetService<IEnumerable<TService >>();public static TService GetService<TService >(this Cat cat) => (TService)cat.GetService(typeof(T));}
上述六个扩展方法帮助我们完成针对单一服务的注册,有时间我们的项目中可能会出现非常多的服务需要注册,如何能够完成针对它们的批量注册会是不错的选择。我们的依赖注入框架提供了针对程序集范围的批量服务注册。为了标识带注册的服务,我们需要在服务实现类型上标注如下这个MapToAttribute类型,并指定服务类型(一般为它实现的接口或者继承的基类)和生命周期。
[AttributeUsage( AttributeTargets.Class, AllowMultiple = true)]public sealed class MapToAttribute: Attribute{ public Type ServiceType { get; }public Lifetime Lifetime { get; } public MapToAttribute(Type serviceType, Lifetime lifetime) { ServiceType = serviceType; Lifetime = lifetime; }}
针对程序集范围的批量服务注册实现在Cat的如下这个Register扩展方法中。如下面的代码片段所示,该方法会从指定程序集中获取所有标注了MapToAttribute特性的类型,并提取出服务类型、实现类型和生命周期模型,然后利用它们批量完成所需的服务注册。
public static class CatExtensions{ public static Cat Register(this Cat cat, Assembly assembly) {var typedAttributes = from type in assembly.GetExportedTypes() let attribute = type.GetCustomAttribute<MapToAttribute>()where attribute != nullselect new { ServiceType = type, Attribute = attribute };foreach (var typedAttribute in typedAttributes) { cat.Register(typedAttribute.Attribute.ServiceType, typedAttribute.ServiceType, typedAttribute.Attribute.Lifetime); }return cat; }}
除了上述这七个用来注册服务的Register扩展方法,我们还为Cat类型定义了如下两个扩展方法,其中CreateService<T>方法以泛型参数的形式指定获取服务实例对应注册的类型,CreateServices<T>方法会提供指定服务类型的所有实例,而CreateChild方法则帮助我们创建一个代表子容器的Cat对象。
public static class CatExtensions{ public static T GetService<T>(this Cat cat) => (T)cat.GetService(typeof(T));public static IEnumerable<T> GetServices<T>(this Cat cat) => cat.GetService<IEnumerable<T>>();public static Cat CreateChild(this Cat cat) => new Cat(cat);}
依赖注入:一个Mini版的依赖注入框架相关推荐
- 一个简易版的T4代码生成框架
对于企业开发来说,代码生成在某种意义上可以极大地提高开发效率和质量.在众多代码生成方案来说,T4是一个不错的选择,今天花了点时间写了一个简易版本的T4代码生成的"框架",该框架仅仅 ...
- 依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...
- everythingtoolbar.dll”或它的一个依赖项。_ASP.NET Core依赖注入最佳实践、提示和技巧...
译者前言 本文译自ABP框架的开发博客<ASP.NET Core Dependency Injection Best Practices, Tips & Tricks>一文(原作者 ...
- Pimple - 一个简单的 PHP 依赖注入容器
链接 官网 WebSite GitHub - Pimple 这是 Pimple 3.x 的文档.如果你正在使用 Pimple 1.x ,请查看 Pimple 1.x 文档. 阅读 Pimple 1.x ...
- 前端第三方依赖文件单独抽出 注入一个公共js文件 html引入方法
虽然前端现在主流使用框架搭建项目,但还是有少许人用jq来开发项目,众所周知,jq引入第三方依赖时每个html页面都需引入,重复动作,依赖越来越多,引入一整坨.因此,我们可以建一个公共js文件,把各页面 ...
- ASP.NET Core中的依赖注入(2):依赖注入(DI)
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...
- php程序设计依赖注入_PHP控制反转和依赖注入
[TOC] PHP和依赖注入 理论知识 要了解控制反转( Inversion of Control ), 我觉得有必要先了解软件设计的一个重要思想:依赖倒置原则(Dependency Inversio ...
- 架构设计之依赖倒置、控制反转与依赖注入
名词解释 依赖:一种模型元素之间的关系的描述.例如类A调用了类B,那么我们说类A依赖于类B. 耦合:一种模型元素之间的关系的描述.例如类A调用了类B或类B调用了类A,那么我们说类A与类B有耦合关系. ...
- spring注入私有字段_Spring字段依赖注入示例
spring注入私有字段 了解如何编写Spring Field Injection示例 . 字段注入是Spring框架 依赖注入的一种 . 在本教程中,我们将编写几个类,并看一看现场注入工程. 有关S ...
最新文章
- 80486保护模式存储管理
- 训练集数量对神经网络光谱的影响
- RunLoop的学习
- numpy(4)-计算数据异常值
- java 做项目踩坑,web项目踩坑过程
- scrapy获取a标签的连接_Python爬虫 scrapy框架初探及实战!
- rhel Linux 网络配置
- 省选+NOI 第八部分 数论
- MyBatis框架学习笔记01:初探MyBatis实现简单查询
- 【转】VLAN(Virtual LAN)“虚拟局域网”
- 知行动手实验室可以用来做什么?
- jadx重新打包_Android改机系列:一.Android一键新机原理刨析
- 2011年12月13日 timeout 与 refused windows clipbrd
- 图像的剪裁——imcrop
- 配天机器人——使用笔记
- 7-6 厘米换算英尺英寸 (15 分)
- 千牛挂“虹(Rainbow)”,域和角色不胜数
- mysql jdbc怎么用问号传参_java – jdbc PreparedStatement中的问号问题
- 5G时代下的光模块:腾讯云计算技术投资5000亿元(附下载)
- c++中static的用法详解
热门文章
- 2007武汉.NET俱乐部沙龙-VS2008、WPF、Silverlight
- Dubbo源码解析之Zookeeper连接
- BZOJ1823:[JSOI2010]满汉全席——题解
- SQL Server Update 所有表的某一列(列名相同,类型相同)数值
- 【Python】猜数小游戏(文件操作)
- Photoshop脚本 使用ExtendScript编写Ps脚本
- angular $watch
- zendframework配置篇
- Base PyQt4, Simple Web APP Framwork
- 标准梯度—lhMorpGradient