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

参考#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.