泛型工厂#
此模式应用于 Microsoft.Extensions.* 和 Microsoft.AspNetCore.*。思路是,可以使用泛型类型作为工厂而不是函数。泛型的参数就是要实例化的类型。比如,我们有一个 IServiceFactory<TService>
,那么可以从 DI 容器中解析 TService
或创建实例(如果它不在容器中)。
public interface IServiceFactory<TService>
{
TService Service { get; }
}
public class ServiceFactory<TService> : IServiceFactory<TService>
{
public ServiceFactory(IServiceProvider provider)
{
Service = (TService)provider.GetService(typeof(TService)) ?? ActivatorUtilities.CreateInstance<TService>(provider);
}
public TService Service { get; }
}
构造函数可以访问任何服务和被请求的泛型类型。这些开放的泛型服务是这样注册的:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(typeof(IServiceFactory<>), typeof(ServiceFactory<>));
}
延迟初始化服务#
有时,需要在构造函数执行之后创建服务。通常的做法是将 IServiceProvider
注入到需要延迟创建实例的服务的构造函数中。
public class Service
{
private readonly IServiceProvider _serviceProvider;
public Service(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IFoo CreateFoo() => _serviceProvider.GetRequiredService<IFoo>();
public IBar CreateBar() => _serviceProvider.GetRequiredService<IBar>();
}
如果提前知道类型,我们可以构建一个 custom service 来提供延迟类型用于封装服务定位器模式(service locator pattern):
public interface ILazy<T>
{
T Value { get; }
}
public class LazyFactory<T> : ILazy<T>
{
private readonly Lazy<T> _lazy;
public LazyFactory(IServiceProvider service)
{
_lazy = new Lazy<T>(() => service.GetRequiredService<T>());
}
public T Value => _lazy.Value;
}
public class Service
{
private readonly ILazy<IFoo> _foo;
private readonly ILazy<IBar> _bar;
public Service(ILazy<IFoo> foo, ILazy<IBar> bar)
{
_foo = foo;
_bar = bar;
}
public IFoo CreateFoo() => _foo.Value;
public IBar CreateBar() => _bar.Value;
}
Lazy 类型只有在访问 .Value
属性时才会实例化类型,可以提高性能。
开放泛型是这样注册的:
public void ConfigureServices(IServiceCollection services)
{
// We register this as transient so it handles both singleton and scoped services
// as the IServiceProvider injected will have the relevant lifetime.
services.AddTransient(typeof(ILazy<>), typeof(LazyFactory<>));
}
如果希望在注册服务时添加具体类型,也可以直接使用 Lazy<T>
:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<Lazy<IFoo>>(sp => new Lazy<IFoo>(() => sp.GetRequiredService<IFoo>());
services.AddTransient<Lazy<IBar>>(sp => new Lazy<IBar>(() => sp.GetRequiredService<IBar>());
}
继承多个接口的类型#
假设有一个实现类型(class)继承了多个接口(interface),并且想通过 ID 容器公开它。内置的 IServiceCollection
不支持,但使用以下模式。
public class FooAndBar : IFoo, IBar
{
// Imagine a useful implementation
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<FooAndBar>();
services.AddSingleton<IFoo>(sp => sp.GetRequiredService<FooAndBar>());
services.AddSingleton<IBar>(sp => sp.GetRequiredService<FooAndBar>());
}
这样在解析 FooAndBar
、IFoo
和 IBar
时将会得到同样的实例。
使用 IServiceProvider 实例化类型#
通常需要注册类型才能从 DI 容器中获取该类型的实例,比如 IServiceProvider.GetService
。这意味着服务需要在容器中注册。如果有些类型是动态发现的(如 MVC 中的控制器),那就无法从 DI 容器中解析出来。
不过好在微软提供了 ActivatorUtilities,它是一个类型工厂,它从 IServiceProvider
中解析出指定类型的实例,假如该类型没有在 DI 容器中注册,它会直接创建出这个类型的实例。
public class MyDependency
{
public MyDependency(ILogger logger) {}
}
public class MyDependencyFactory
{
private readonly IServiceProvider _serviceProvider;
public MyDependencyFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public MyDependency GetInstance()
{
return ActivatorUtilities.CreateInstance<MyDependency>(_serviceProvider);
}
}
可以用 CreateFactory
构建更优化的版本,它会预先计算指定类型的构造函数,并为其构建一个工厂方法(它返回一个委托)。
public class MyDependencyFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly ObjectFactory _factory;
public MyDependencyFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_factory = ActivatorUtilities.CreateFactory(typeof(MyDependency), Type.EmptyTypes);
}
public MyDependency GetInstance()
{
return (MyDependency) _factory(_serviceProvider, null);
}
}
Warning
DI 容器不会释放使用此 API 创建的一次性实例。
所以记得手动 Dispose()
释放资源,否则可能这些实例可能会一直占用内存,导致内存泄露。
https://github.com/davidfowl/DotNetCodingPatterns/issues/13
所以最好这样使用:
using (var scope = serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<DBContext>();
// ...
}
在泛型类型中缓存单例#
如果需要缓存某个类型的实例,可以将它存储在泛型类型的静态字段中。
public class Factory
{
public T Create<T>() where T : new()
{
return Cache<T>.Instance;
}
private static class Cache<T> where T : new()
{
public static readonly T Instance = new();
}
}
可以使用 JIT 代替较慢的 ConcurrentDictionary<Type, T>
来缓存实例。它还可用于缓存昂贵的对象创建过程:
public class Factory
{
public T Create<T>() where T : new()
{
return Cache<T>.Instance;
}
private static class Cache<T> where T : new()
{
public static readonly T Instance = CallExpensiveMethodToBuildANewInstance();
private static T CallExpensiveMethodToBuildANewInstance()
{
// 假设这里经历了非常复杂的过程,才得到了类型 T 的实例
return instanceOfT;
}
}
}
使用 IOptions 时访问 DI 容器中的服务#
options 模式应用于 Microsoft.Extensions.*
和 Microsoft.AspNetCore.*
。它允许任何人注册回调用于更新配置库的 POCO。有时我们希望在配置 options 时访问服务,有多种方法可以做到:
public class LibraryOptions
{
public int Setting { get; set; }
}
public class MyConfigureOptions : IConfigureOptions<LibraryOptions>
{
private readonly ISomeService _service;
public MyConfigureOptions(ISomeService service)
{
_service = service;
}
public void Configure(LibraryOptions options)
{
options.Setting = _service.ComputeSetting();
}
}
然后注册:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfigureOptions<LibraryOptions>, MyConfigureOptions>();
}
不过这种写法太繁琐了,微软提供了一些 helpers 来简化这一过程:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions<LibraryOptions>()
.Configure<ISomeService>((options, service) =>
{
options.Setting = service.ComputeSetting();
});
}
异步 Socket Server#
下面是一个使用现代化 .NET APIs 的异步 server:
- 基于 Task 接口,异步 accept/read/write
- 对于缓冲区进行切片的范围语法
using var listenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, 8080));
Console.WriteLine($"Listening on {listenSocket.LocalEndPoint}");
listenSocket.Listen();
while (true)
{
// Wait for a new connection to arrive
var connection = await listenSocket.AcceptAsync();
// We got a new connection spawn a task to so that we can echo the contents of the connection
_ = Task.Run(async () =>
{
var buffer = new byte[4096];
try
{
while (true)
{
int read = await connection.ReceiveAsync(buffer, SocketFlags.None);
if (read == 0)
{
break;
}
await connection.SendAsync(buffer[..read], SocketFlags.None);
}
}
finally
{
connection.Dispose();
}
});
}
异步 Socket Client#
下面是一个使用现代化 .NET APIs 的异步 client:
- 基于 Task 的接口,异步建立连接
- 通过 Socket 创建 NetworkStream 以使用 CopyToAsync,这是一个 helper,可以轻松地将内容从一个 Stream 复制到另一个 Stream。
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 8080));
Console.WriteLine("Type into the console to echo the contents");
var ns = new NetworkStream(socket);
var readTask = Console.OpenStandardInput().CopyToAsync(ns);
var writeTask = ns.CopyToAsync(Console.OpenStandardOutput());
// Quit if any of the tasks complete
await Task.WhenAny(readTask, writeTask);