依赖注入:一个Mini版的依赖注入框架

前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍。为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类似的原理创建了一个简易版本的依赖注入框架,也就是我们在前面多次提及的Cat。

源代码下载

普通服务的注册与消费
泛型服务的注册与消费
多服务实例的提供
服务实例的生命周期

一、编程体验

虽然我们对这个名为Cat的依赖注入框架进行了最大限度的简化,但是与.NET Core框架内部使用的真实依赖注入框架相比,Cat不仅采用了一致的设计,而且几乎具备了后者所有的功能特性。为了让大家对Cat具有一个感官的认识,我们先来演示一下如何利用它来提供我们所需的服务实例。

作为依赖注入容器的Cat对象不仅仅作为服务实例的提供者,它同时还需要维护着服务实例的生命周期。Cat提供了三种生命周期模式,如果要了解它们之间的差异,就必须对多个Cat之间的层次关系有充分的认识。一个代表依赖注入容器的Cat对象用来创建其他的Cat对象,后者视前者为“父容器”,所以多个Cat对象通过其“父子关系”维系一个树形层次化结构。不过这仅仅是一个逻辑结构而已,实际上每个Cat对象只会按照下图所示的方式引用整棵树的根。

640?wx_fmt=png

在了解了多个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对象,后者只会被创建一次。

640?wx_fmt=png

除了提供类似于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方法被调用的时候。

640?wx_fmt=png

二、设计与实现

在完成针对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对象。下图体现了服务注册核心三要素和链表结构。

640?wx_fmt=png

在了解了表示服务注册的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字段表示的集合中。

640?wx_fmt=png

由当前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;
}

//Generic
if (serviceType.IsGenericType && !_registries.ContainsKey(serviceType))
{
var definition = serviceType.GetGenericTypeDefinition();
return _registries.TryGetValue(definition, out registry)
? GetServiceCore(registry, serviceType.GetGenericArguments())
: null;
}

//Normal
return _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 != null
select 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);
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/313243.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET Core 3.0 新 JSON API - JsonSerializer

JsonSerializer 前面几节的内容可能稍微有点底层&#xff0c;我们大部分时候可能只需要对C#的类进行串行化或者将JSON数据反串行化成C#类&#xff0c;在.NET Core 3.0里面&#xff0c;我们可以使用JsonSerializer这个类来做这些事情。 例子 还是使用之前用到的json数据&#xf…

Caffe查看每一层学习出来的pattern

Filter visualization http://www.cnblogs.com/dupuleng/articles/4244877.html 这一节参考http://nbviewer.ipython.org/github/BVLC/caffe/blob/master/examples/filter_visualization.ipynb&#xff0c;主要介绍如何显示每一层的参数及输出&#xff0c;这一部分非常重要&am…

.NET Core 3.0 新 JSON API - Utf8JsonWriter

Utf8JsonWriter类 下面研究一下如何写入json文件。这里需要使用Utf8JsonWriter类。 直接看代码&#xff1a; 这个类需要传递的参数类型是Stream或者Buffer&#xff0c;也就是向Stream或Buffer里面写入数据。 那么就提供一个buffer&#xff1a; 下面单独写一个方法&#xff0c;来…

python查看CNN训练模型参数

参照&#xff1a;http://blog.csdn.net/u011762313/article/details/49851795 #!/usr/bin/env python# 引入“咖啡” import caffeimport numpy as np# 使输出的参数完全显示 # 若没有这一句&#xff0c;因为参数太多&#xff0c;中间会以省略号“……”的形式代替 np.set_prin…

Bumblebee微服务网关之consul服务发现

网关需要维护相关负载的服务器&#xff0c;手动添加相对来说是一件比较麻烦的工作&#xff1b;为了解决这一问题组件扩展了一个基于consul服务发现插件&#xff0c;通过配置这个插件和启用后网关会自动从consul服务中获取服务并添加到网关对应的路由负载规则中。引用插件Bumble…

Github带来的不止是开源,还有折叠的认知

几乎每个程序员都知道github&#xff0c;但是知道目前上面有多少repositories的估计没几个。Z哥今天去看了下&#xff0c;最新的数量显示是1.39亿个。▲截图来自于github.com而这个数字在2008年那会只是3.3万个。这个增长速度可谓真的是“爆炸式增长”。与此同时&#xff0c;大…

最优间隔分类器-SVM

http://blog.csdn.net/Andrewseu/article/details/46991541 本讲大纲&#xff1a; 1.最优间隔分类器(optimal margin classifier) 2.原始/对偶优化问题&#xff08;KKT&#xff09;&#xff08;primal/dual optimization problem&#xff09; 3.SVM对偶(SVM dual) 4.核方法(…

自动给 Asp.Net Core Api 增加 ApiVersionNeutral

自动给 Asp.Net Core Api 增加 ApiVersionNeutralIntro新增加一个 Controller 的时候&#xff0c;经常忘记在 Controller 上增加 ApiVersion &#xff0c;结果就导致前端使用指定的 ApiVersion 访问的时候就会失败&#xff0c;不支持的 Api 版本。错误信息如下&#xff1a;{ &q…

K-means与高斯混合模型

K-means http://blog.pluskid.org/?p17 Clustering 中文翻译作“聚类”&#xff0c;简单地说就是把相似的东西分到一组&#xff0c;同 Classification (分类)不同&#xff0c;对于一个 classifier &#xff0c;通常需要你告诉它“这个东西被分为某某类”这样一些例子&#xf…

轻量级ORM《sqlcommon》第一个版本发布了

一、sqlcommon的特色1. 轻量级&#xff0c;整个包只有123kb。2. 性能好&#xff0c;自测。。。3. API和功能简单、代码简短、可维护性好基本都能看懂。这个点我认为很重要&#xff0c;你不用为了实现一个需求而四处查资料&#xff0c;这意味着这个包你可以自行维护修改&#xf…

从基于直方图的Graph-Cut到Grab-Cut

http://blog.csdn.net/zouxy09/article/details/8534954 区别&#xff1a; &#xff08;1&#xff09;Graph Cut的目标和背景的模型是灰度直方图&#xff0c;Grab Cut取代为RGB三通道的混合高斯模型GMM&#xff1b; &#xff08;2&#xff09;Graph Cut的能量最小化&#xf…

1024程序员节活动继续:购书优惠劵,折后再折,赶紧来抢啊

1024程序员节当当网计算机图书每满100减50&#xff01;满200减100&#xff01;满300-150&#xff01;机械工业出版社华章公司联合当当网特意为【DotNET技术圈】用户申请了一批可与满减叠加使用的“满200减30”的图书优惠码&#xff0c;优惠码使用后相当于&#xff1a;400减230 …

Shape Context

http://blog.csdn.net/u012507022/article/details/52437149 形状上下文特征是一种很流行的形状描述子&#xff0c;多用于目标识别&#xff0c;它采用一种基于形状轮廓的特征描述方法,其在对数极坐标系下利用直方图描述形状特征能够很好地反映轮廓上采样点的分布情况。 形状上下…

使用Magicodes.SwaggerUI快速配置SwaggerUI以及设置API分组

Magicodes.SwaggerUI快速配置和集成SwaggerUI特点通过配置文件简单配置即可完成SwaggerUI的API格式JSON生成和集成支持API分组和隐藏支持自定义页面和验证Nuget包联系我们订阅号关注“麦扣聊技术”微信订阅号可以获得最新文章、教程、文档。QQ群编程交流群<85318032>产品…

机器学习四大降维方法

http://www.36dsj.com/archives/26723 引言 机器学习领域中所谓的降维就是指采用某种映射方法&#xff0c;将原高维空间中的数据点映射到低维度的空间中。降维的本质是学习一个映射函数 f : x->y&#xff0c;其中x是原始数据点的表达&#xff0c;目前最多使用向量表达形式。…

深入研究.NET Core的本地化机制

ASP.NET Core中提供了一些本地化服务和中间件&#xff0c;可将网站本地化为不同的语言文化。ASP.NET Core中我们可以使用Microsoft.AspNetCore.Localization库来实现本地化。在.NET Core 2.0以上版本, Microsoft.AspNetCore.Localization已经包含在了Microsoft.AspNetCore.All中…

.Net Core 3.0 IdentityServer4 快速入门02

.Net Core 3.0 IdentityServer4 快速入门—— resource owner password credentials&#xff08;密码模式&#xff09;一、前言OAuth2.0默认有四种授权模式&#xff08;GrantType&#xff09;&#xff1a;1&#xff09;授权码模式2&#xff09;简化模式3&#xff09;密码模式&a…

.Net Core3.0 日志 logging

多年的经验&#xff0c;日志记录是软件开发的重要组成部分。没有日志记录机制的系统不是完善的系统。在开发阶段可以通过debug附件进程进行交互调试&#xff0c;可以检测到一些问题&#xff0c;但是在上线之后&#xff0c;日志的记录起到至关重要的作用。它可使我们在系统出现问…

在微软工作一年,我学会了什么

大家好&#xff0c;我是运营小马。正如我们所知道的那样&#xff0c;10.23日 &#xff0c;崔庆才因为写文写得很痛苦&#xff0c;将公众号转给我运营。10.24 我兴致勃勃又小心翼翼的宣布了我要运营10.25 崔庆才回来了&#xff0c;他说他有喷薄而出抑制不住的写作欲望&#xff0…

Eclipse调试方法

http://blog.jobbole.com/93421/ 一、Eclipse调试介绍 二、Eclipse中和Debug相关的视图 2.1 Debug View2.2 Variables View2.3 Breakpoints View2.4 Expressions View2.5 Display View 三、Debug 3.1 设置断点 3.2 调试程序 3.2.1 调试本地 Java 语言程序 3.3.2 远程调试 一、…