服务类型
The minimal part of service and dependency resolution specification is Service Type. It allows container to find corresponding service registration。 服务与依赖解析规范中,最小的部分是“服务类型”。容器将会根据服务类型查找到服务注册信息。
public interface IDependency {}
public class Dependency : IDependency {}
public class Foo { public Foo(IDependency dependency) { /*...*/} }
var container = new Container();
container.Register<IDependency, Dependency>();
container.Register<Foo>();
container.Resolve<Foo>();
在上述示例中,容器将通过 IDependency 参数类型查找到有关 IDependency 的注册信息。
除了服务类型以外,还需要提供的信息包括:
- 请求服务类型
- 服务标识(Service Key)
- 未解析服务处理
- 依赖无法解析时默认值
- 自定义依赖基元值
服务标识(Service Key)
Given you registered multiple services of the same Service Type, Service Key provides the easiest way to find specific service. 当同一个“服务类”注册了多个“实现类”时,Service Key 能提供最为简便的方法用于查找特定的“实现类”。
示例 :注册多个类型时,直接解析对象抛出异常
- IDependecy 接口 注册了多个类型(XDependency,YDependency),Foo 类的构造函数需要提供一个 IDependecy 对象
- 直接使用容器解析 Foo 对象,则抛出异常: Error.ExpectedSingleDefaultFactory ```csharp public enum SomeKind { In, Out }
public interface IDependency {} public class XDependency : IDependency {} public class YDependency : IDependency {} public class Foo { public IDependency Dependency; public Foo(IDependency dependency) => Dependency = dependency; }
var container = new Container();
container.Register
var ex = Assert.Throws
Assert.AreEqual(Error.NameOf(Error.ExpectedSingleDefaultFactory), ex.ErrorName);
<a name="HysoZ"></a>
#### 示例 :注册类型时,使用 Service Key 标识类型,在 Arg.Of 方法利用 Service Key 指定特定类型
- 使用 枚举类型区分 IDependency (SomeKind 作为 Service Key)
- 注册 Foo 类型,使用“表达式”指定特定的构造函数以及特定的参数类型
- Service Key 推荐使用“枚举”类型,不推荐“字符串”类型:
- strings are more fragile for refactoring and do not statically checked by compiler
- string 类型不利于重构同时无法在编译器中进行静态检查
```csharp
var container = new Container();
container.Register<IDependency, XDependency>(serviceKey: SomeKind.In);
container.Register<IDependency, YDependency>(serviceKey: SomeKind.Out);
container.Register<Foo>(made: Made.Of(() => new Foo(Arg.Of<IDependency>(SomeKind.In))));
var foo = container.Resolve<Foo>();
Assert.IsInstanceOf<XDependency>(foo.Dependency);
示例 :注册类型时,使用 Service Key 标识类型,在 Parameters.Of.Type 方法利用 Service Key 指定特定类型
var container = new Container();
container.Register<IDependency, XDependency>(serviceKey: SomeKind.In);
container.Register<IDependency, YDependency>(serviceKey: SomeKind.Out);
container.Register<Foo>(made: Parameters.Of.Type<IDependency>(serviceKey: SomeKind.In));
var foo = container.Resolve<Foo>();
Assert.IsInstanceOf<XDependency>(foo.Dependency);
未解析服务处理
Unresolved means that Container unable to resolve either service itself or one of its dependencies. “未解析”指的是容器无法解析服务或者它所依赖的服务。
原因如下:
- 服务或它所依赖的服务未注册
- 未设定回退规则:Rules.UnknownServiceResolvers
- 忘记服务标识、请求服务或者条件
DryIoc 支持两种可选方式用于处理解析错误:
- 引发相应异常
- 返回默认值,通常是 null
注意:抛出异常是除了属性/字段以外所有内容默认的处理方式。因为 属性/ 字段 是允许在创建对象时为空的。
使用方法示例:
- Resolve
(ifUnresolved: IfUnresolved.Throw) (or just Resolve ()) - Resolve
(ifUnresolved: IfUnresolved.ReturnDefault) 示例 :IfUnresolved.ReturnDefault 与 IfUnresolved.Throw 的基本用法
```csharp public class Foo { public IDependency Dependency { get; } public Foo(IDependency dependency) => Dependency = dependency; }
var container = new Container();
// 为 Foo 指定构造函数,为 Foo 构造函数的 dependency 参数指定解析错误时返回默认值
container.Register
// 若无法解析 Foo 对象时,抛出异常
var foo = container.Resolve
foo = container.Resolve
// Assert通过,foo对象不为空 Assert.IsNotNull(foo);
// Assert通过,foo对象的Dependency属性未空(因为IDependency未注册) Assert.IsNull(foo.Dependency);
<a name="mzKFu"></a>
#### 示例 :属性默认为 IfUnresolved.ReturnDefault,通过手动设置 IfUnresolved.Throw ,在解析错误时抛出异常
```csharp
public class Bar
{
public IDependency Dependency { get; set; }
}
var container = new Container();
// IfUnresolved.Throw 标记 dependency 属性在解析错误时抛出异常
container.Register<Bar>(made: Made.Of(() => new Bar() { Dependency = Arg.Of<IDependency>(IfUnresolved.Throw) }));
// Assert通过,因为 IDependency 未注册,因此抛出异常
var ex = Assert.Throws<ContainerException>(() => container.Resolve<Bar>());
Assert.AreEqual(Error.NameOf(Error.UnableToResolveUnknownService), ex.ErrorName);
// IfAlreadyRegistered.Replace 表示 覆盖已有的注册
container.Register<Bar>(ifAlreadyRegistered: IfAlreadyRegistered.Replace);
var bar = container.Resolve<Bar>();
// Assert通过,bar 对象未空
Assert.IsNotNull(bar);
// Assert通过,bar 对象的 Dependency 属性为空(属性默认返回空值 IfUnresolved.ReturnDefault)
Assert.IsNull(bar.Dependency);
未解析依赖项的默认值
示例 :设置 IfUnresolved.ReturnDefault 与 defaultValue
- 通过 Parameters.Of.Name 方法为特定名称的参数进行设置 ```csharp public class Foo { public int Answer; public Foo(int answer) => Answer = answer; }
var container = new Container();
// 使用 Parameters.Of 方法 设置 answer 参数默认值为 42
container.Register
var foo = container.Resolve
// Assert通过 Assert.AreEqual(42, foo.Answer);
<a name="aeDpa"></a>
### <br />
<a name="q8ZQU"></a>
## 可选参数
> DryIoc 支持构造函数或工厂方法的可选参数。
<a name="LD8I9"></a>
#### 示例 :支持构造函数或工厂方法设置参数的可选值
```csharp
public IDependency Dependency;
public int Answer;
public Foo(IDependency dependency = null, int answer = 42)
{
Dependency = dependency;
Answer = answer;
}
var container = new Container();
container.Register<Foo>();
var foo = container.Resolve<Foo>();
Assert.IsNull(foo.Dependency);
Assert.AreEqual(42, foo.Answer);
注入原生类型的值
DryIoc 支持多种方法想原生类型的参数注入值
示例 :以“实例”(Instance)方式注入值
public class Foo
{
public string Name;
public Foo(string name) => Name = name;
}
[Test]
public void Example_via_RegisterInstance()
{
var c = new Container();
c.Register<Foo>();
c.RegisterInstance("my string");
Assert.AreEqual("my string", c.Resolve<Foo>().Name);
}
示例 :以“实例”(Instance)+“服务标识”(Service Key)方式注入值
- 使用 Parameters.Of.Type 为指定类型的参数注入值 ```csharp public class Foo { public string Name; public Foo(string name) => Name = name; }
[Test] public void Example_via_RegisterInstance_and_ServiceKey() { var c = new Container();
c.Register<Foo>(made: Parameters.Of.Type<string>(serviceKey: "someSetting"));
c.RegisterInstance("my string", serviceKey: "someSetting");
Assert.AreEqual("my string", c.Resolve<Foo>().Name);
}
<a name="zHdaz"></a>
#### 示例 :以指定“构造函数”(Constructor)+“服务标识”(Service Key)方式注入值
```csharp
public class Foo
{
public string Name;
public Foo(string name) => Name = name;
}
[Test]
public void Example_via_strongly_typed_spec()
{
var c = new Container();
c.Register<Foo>(Made.Of(() => new Foo(Arg.Of<string>("someSetting"))));
c.RegisterInstance("my string", serviceKey: "someSetting");
Assert.AreEqual("my string", c.Resolve<Foo>().Name);
}
示例 :以指定“构造函数”(Constructor)+“自定义值”方式注入值
public class Foo
{
public string Name;
public Foo(string name) => Name = name;
}
[Test]
public void Example_via_strongly_typed_spec_and_direct_argument_spec()
{
var c = new Container();
c.Register<Foo>(Made.Of(() => new Foo(Arg.Index<string>(0)), _ => "my string"));
Assert.AreEqual("my string", c.Resolve<Foo>().Name);
}
DryIoc 源码:
//
// 摘要:
// Defines factory method using expression of constructor call (with properties),
// or static method call.
//
// 参数:
// serviceReturningExpr:
// Expression tree with call to constructor with properties:
// or static method call
//
// argValues:
// (optional) Primitive custom values for dependencies.
//
// 类型参数:
// TService:
// Type with constructor or static method.
//
// 返回结果:
// New Made specification.
public static TypedMade<TService> Of<TService>(Expression<Func<TService>> serviceReturningExpr, params Func<Request, object>[] argValues);
示例 :以“委托”(delegate)方式注入值
- 不推荐使用这种方法,不便于后期的重构修改。 ```csharp public class Foo { public string Name; public Foo(string name) => Name = name; }
[Test] public void Example_via_RegisterDelegate() { var c = new Container();
c.RegisterDelegate<Foo>(() => new Foo("my string"));
Assert.AreEqual("my string", c.Resolve<Foo>().Name);
}
<a name="IWmSr"></a>
# 自定义依赖值
> DryIoc supports the injecting of custom (non-registered) values as a parameter, property, or field. But using the constant value is not very interesting, so let's look at the case when the value depends on the object graph context. It is the common pattern to pass the holder Type as a parameter when utilizing the "Logger" object.
> DryIoc 支持通过【参数】、【属性】或【字段】的方式注入值。使用常量这种方式十分地无趣,让我们来看看基于上下文的值依赖的示例。在初始化“日志处理对象”(Logger)时,常常需要将持有它所持有的类型作为作为参数传递进去。
<a name="LR3G2"></a>
#### 示例 :自定义依赖值:通过 Request 类的 Parent 属性获取“父请求”的相关信息,例如:父请求的“实现类”(ImplementationType)
- 解析 ILogger 接口时,利用 req.Parent.ImplementationType 获取父请求的实现型(User),使其注入为 Logger 构造函数 logger 参数的值
```csharp
public interface ILogger
{
Type ContextType { get; }
}
public class Logger : ILogger
{
public Type ContextType { get; }
public Logger(Type type) => ContextType = type;
}
public class User
{
public ILogger Logger { get; }
public User(ILogger logger) => Logger = logger;
}
var container = new Container();
container.Register<User>();
container.Register<ILogger, Logger>(made: Parameters.Of.Type<Type>(req => req.Parent.ImplementationType));
var user = container.Resolve<User>();
Assert.AreEqual(typeof(User), user.Logger.ContextType);
除了 Parameters.Of.Type 外,Parameters 类还有其他关于 Name 和 Details 的方法。PropertiesAndFileds 类型同样如此。
注册时设置条件
有些时候依赖注入需要依据注入的具体位置而定。例如在某种 Service 中需要注入某种 ILogger,在另外一种 Service 中需要注入另外一种 ILogger。除了通过“服务标识”(service key)这种方式区分两种 ILogger 外,还可以使用设置条件的方式来解决这类问题。
示例 :注册时设置条件,依据不同的服务类注入不同的依赖
- Setup 类:Base class to store optional DryIoc.Factory settings . 用于存储有关 DryIoc.Factory 可选配置。 ```csharp public interface ISmallFish {} public interface IBigFish {}
public class Tuna : IBigFish { public ILogger Logger; public Tuna(ILogger logger) => Logger = logger; }
public interface ILogger {} public class FileLogger : ILogger {} public class DbLogger : ILogger {}
var container = new Container();
container.Register
// 父请求中服务类继承自 ISmallFish
container.Register
// 父请求中服务类继承自 ISmallFish
container.Register
var fish = container.Resolve
<a name="eB08N"></a>
# API规范
> The entry point for specifying service or dependency resolution is Made class. It contains three optional parts:
> - FactoryMethod may provide full specification including:
> - Constructor, method, property or field to be used for service creation,
> - And optionally parameters and properties/fields specification.
> - Parameters is responsible for parameters specification only.
> - PropertiesAndFields is responsible for properties and fields specification only.
>
> Made 类作为指定服务或依赖解析的切入点,它包含以下三个可选部分:
> - FactoryMethod 提供完整的规范:
> - 构造函数,方法,属性/字段可用于服务创建,
> - 以及可选的参数或属性/字段等。
> - Parameters 仅负责参数部分的规范。
> - PropertiesAndFields 仅负责属性或字段的规范。
<a name="BdENP"></a>
#### 示例 :Made.Of 方法用法示例
```csharp
container.Register<IFoo, Foo>(made: Made.Of(
factoryMethod: r => FactoryMethod.Of(typeof(Foo).GetConstructorOrNull(args: new[] { typeof(IDependency) })),
parameters: Parameters.Of.Type<IDependency>(requiredServiceType: typeof(TestDependency)),
propertiesAndFields: PropertiesAndFields.Auto));
反射使用的频率并不高,因为 Made 方法支持强类型表达式机制
示例 :Made.Of 方法使用“表达式”指定构造函数与参数
new Foo(..) 指定了构造函数
- Arg.Of
() 指定容器为构造函数的dependency 参数 注入 TestDependency ```csharp public interface IFoo {} public class Foo : IFoo { public Foo(IDependency dependency) {} } public interface IDependency {} public class TestDependency : IDependency {}
var container = new Container();
container.Register
container.Register
<a name="vF9Zq"></a>
#### 示例 :Parameters.Of 方法用于定义参数
- 在 Parameters.Of.Type 方法中利用 requiredServiceType 指定服务类型
```csharp
public interface IFoo {}
public class Foo : IFoo
{
public Foo(IDependency dependency) {}
}
public interface IDependency {}
public class TestDependency : IDependency {}
var container = new Container();
container.Register<TestDependency>();
// 由于容器注册是 TestDependency,因此将 Foo 构造函数的 dependency 参数定义为请求注入 TestDependency
container.Register<IFoo, Foo>(made: Parameters.Of.Type<IDependency>(requiredServiceType: typeof(TestDependency)));
示例 :Parameters 方法的“链式调用”
- Parameters 方法支持“链式调用”,其中 Details 为底层的方法,Name 和 Type 则是为了提高易用性的封装方法。 ```csharp public enum SomeKind { In, Out }
public class Foo { public Foo(IDisposableResource parameter1, int parameter2, IDependency parameter3) {} }
public interface IDisposableResource : IDisposable {} public interface IDependency {}
container.Register
.Details((req, paramInfo) => paramInfo.ParameterType.IsAssignableTo<IDisposable>()
? ServiceDetails.Of(ifUnresolved: IfUnresolved.ReturnDefault)
: null) // 'null' 表示使用默认的参数方案
.Name("parameter2", serviceKey: "p2")
.Type<IDependency>(serviceKey: SomeKind.In));
<a name="ca2hj"></a>
# 默认约定
> DryIco默认约定如下:
> - 基于服务类(Service Type)构造函数和工厂方法参数进入的方式等价于 ParameterType 配置 IfUnresolved.Throw 策略。
> - 如果不显示指定属性或字段的注入规则,则完全不会注入。如果设置了 PropertiesAndFields.Auto ,那么所有可赋值的非原生类的属性和字段则会以 IfUnresolved.ReturnDefault 策略注入值。
> - 原生类型的参数、属性、字段将被视作为普通的服务类型:例如,DryIoc并不会禁止 bool 或 int 类型的注入。
<a name="z5SEB"></a>
# 通过参数名称匹配服务标识(Service Key)
<a name="xfa4o"></a>
#### 示例 :根据不同的参数名称匹配不同的服务标识(Service Key)
```csharp
public interface ITest { }
public class A : ITest { }
public class B : ITest { }
public class ExampleClass
{
public ExampleClass(ITest a, ITest b) {}
}
var c = new Container();
c.Register<ITest, A>(serviceKey: "a");
c.Register<ITest, B>(serviceKey: "b");
c.Register<ExampleClass>(made: Made.Of(parameters: Parameters.Of
.Name("a", serviceKey: "a")
.Name("b", serviceKey: "b")));
var example = c.Resolve<ExampleClass>();
Assert.IsNotNull(example);
示例 :根据不同的参数名称匹配不同的服务标识(使用 ParameterSelector )
- 不需要使用 Made.Of 方法,因为 ParameterSelector( Parameter.Of 返回值 )可以隐式转换为 Made 对象 ```csharp public interface ITest { } public class A : ITest { } public class B : ITest { } public class ExampleClass { public ExampleClass(ITest a, ITest b) {} }
var c = new Container();
c.Register
c.Register
Assert.IsNotNull(c.Resolve
DryIoc 源码:ParameterSelector 隐式转换为 Made
```csharp
public class Made
{
//...
public static implicit operator Made(ParameterSelector parameters);
}
示例 :参数名称与服务标识同名时自动匹配
- 这种方式很脆弱,当 ExampleClass 添加新的参数,该参数名称并不作为服务标识(service key)时,无法正确解析对象。 ```csharp public interface ITest { } public class A : ITest { } public class B : ITest { } public class ExampleClass { public ExampleClass(ITest a, ITest b) {} }
var c = new Container();
c.Register
c.Register
Assert.IsNotNull(c.Resolve
<a name="FjXQX"></a>
#### 示例 :使用“表达式”指定构造函数与参数,能在构造函数发生变化时获得编译器异常提示
```csharp
var c = new Container();
c.Register<ITest, A>(serviceKey: "a");
c.Register<ITest, B>(serviceKey: "b");
c.Register<ExampleClass>(made: Made.Of(() =>
new ExampleClass(
Arg.Of<ITest>("a"),
Arg.Of<ITest>("b"))));
Assert.IsNotNull(c.Resolve<ExampleClass>());
示例 :使用 Rule 实现参数名称与服务标识同名时自动匹配
- 在 rules 中 使用 Parameters.Of.Details 进行详细的针对性(请求类型为ExampleClass)设置
```csharp
var c = new Container(rules =>
rules.With(parameters:
Parameters.Of.Details(
(req, parInfo) => req.ServiceType == typeof(ExampleClass)
? ServiceDetails.Of(serviceKey: parInfo.Name)
: null)));
c.Register
Assert.IsNotNull(c.Resolve