myesn

myEsn2E9

hi
github

.NET 编码模式

泛型工厂#

此模式应用于 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>());
}

这样在解析 FooAndBarIFooIBar 时将会得到同样的实例。

使用 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);

参考#

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。