首页 > 其他分享 >.NET coding patterns(davidfowl)

.NET coding patterns(davidfowl)

时间:2023-04-22 17:55:05浏览次数:46  
标签:davidfowl service serviceProvider class patterns services new NET public

Table of contents

Generics types as a factory

This pattern is used in Microsoft.Extensions.* and in Microsoft.AspNetCore.*. The idea is that you can use a generic type as a factory instead of a function. The type argument is the type you want to instantiate. Consider the example below where we have an IServiceFactory<TService> that can resolve the TService from the DI container or creates an instance if it's not in the container.

public interface IServiceFactory<TService>
{
    TService Service { get; }
}

public class ServiceFactory<TService> : IServiceFactory<TService>
{
    public ServiceFactory(IServiceProvider service)
    {
        Service = (TService)service.GetService(typeof(TService)) ?? ActivatorUtilities.CreateInstance<TService>(service);
    }

    public TService Service { get; }
}

The constructor has a access to any service and the generic type being requested. These open generic services are registered like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient(typeof(IServiceFactory<>), typeof(ServiceFactory<>));
}

Lazy initialization of services

Sometimes it's necessary to create services later than a constructor. The usually work around for this is to inject and IServiceProvider into the constructor of the service that needs to lazily create the instance.

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

If the types are known ahead of time, we can build a custom service provider lazy type to encapsulate the 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;
}

Registered like this:

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> could also be used as-is if added with a concrete type at service registration time:

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

Single implementation multiple interfaces

Let's say you had a type that implemented multiple interfaces and you wanted to expose it using the DI container. The built in IServiceCollection type doesn't natively support this but it's easy to emulate using the following pattern.

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

This will let me resolve FooAndBarIFoo and IBar and it will give me the same instance.

Creating instances of types from an IServiceProvider

Usually you need to register a type in order to instantiate instances of a type from the DI container, somebody needs to call IServiceProvider.GetService. This means that the service needs to be registered in the container. This may not be desirable if these types are discovered dynamically (like controllers in MVC). There's a useful utility called ActivatorUtilities that can be used as a factory for types that haven't been registered, but have dependencies that are registered in the DI container.

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

You can build a more optimized version of this uses the CreateFactory. This will pre-calculate the constructor based on the types passed and build a factory for it.

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

NOTE: Disposable instances created with this API will not be disposed by the DI container.

Caching singletons in generic types

If you need to cache an instance of something based on type, then you can store it on a static field of a generic type.

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

You can use the JIT to cache instances on your behalf instead of a slower ConcurrentDictionary<Type, T>. It can also be used to cache the expensive object creation process:

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()
        {
            // Imagine a really complex process to construct T
            return instance.
        }
    }
}

Resolving services when using IOptions<T>

The options pattern is used in Microsoft.Extensions.* and in Microsoft.AspNetCore.*. It allows anyone to register a callback to mutate a POCO used to configure a library. Sometimes you'd like access to services when configuring these options. There are multiple ways to do this:

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

Followed by the registration.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IConfigureOptions<LibraryOptions>, MyConfigureOptions>();
}

That's a bit verbose, so we added some helpers to make it easier.

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions<LibraryOptions>()
            .Configure<ISomeService>((options, service) =>
            {
                options.Setting = service.ComputeSetting();
            });
}

 

 

Asynchronous Socket Server

Below is a modern asynchronous server using modern .NET APIs:

  • Task based APIs for asynchronous accept/read/write
  • Range syntax for slicing the buffer
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();
        }
    });
}

Asynchronous Socket Client

Below is a modern asynchronous client using modern .NET APIs:

  • Uses Task based APIs to establish the connection.
  • Creates a NetworkStream over the Socket in order to use CopyToAsync, a helper that makes it easy to copy content from one Stream to another.
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);

 

https://github.com/davidfowl/DotNetCodingPatterns

标签:davidfowl,service,serviceProvider,class,patterns,services,new,NET,public
From: https://www.cnblogs.com/thingk/p/17343596.html

相关文章

  • 【OMNET++网络仿真系列学习笔记-1】Ubuntu 22.04版本安装OMNET++6.0版本及各类报错合
    本章目录前言第一步:下载6.0压缩包第二步:解压并安装第三步:启动环境变量第四步:遇到的问题第五步:./configure编译结束第六步:验证安装是否可以正常运行?第七步:验证IDE总结:写在后面的话前言本篇文章记录了22.04版本Ubuntu安装OMNET++6.0版本及各类报错合集解决方案,途中遇到了无数问题,很......
  • k3s 基础 —— 配置 kubernetes dashboard
    安装请参考部署仪表盘GITHUB_URL=https://github.com/kubernetes/dashboard/releasesVERSION_KUBE_DASHBOARD=$(curl-w'%{url_effective}'-I-L-s-S${GITHUB_URL}/latest-o/dev/null|sed-e's|.*/||')sudok3skubectlcreate-fhttps://raw.githu......
  • 装了.Net 7.0后,工程框架用 net6 的 dotnet watch 出错临时解决方案 Could not load fi
    升级vs或者装了.Net7.0后,工程框架用net6的dotnetwatch出错‘Unhandledexception.System.IO.FileNotFoundException:Couldnotloadfileorassembly‘System.Runtime,Version=7.0.0.0’   临时解决方案:工程目录下建立global.json文件指定编译框架{"......
  • .Net 序列化
    .Net序列化将实体转化为流的形式,传递给他人。他人再反序列化就可以得到实体二进制已弃用,存在危险vartbLabel=newDataTable("tbLabel");varms=newMemoryStream();tbLabel.Columns.Add("cWorkOrder");tbLabel.Rows.Add(new[]{"123"});BinaryFormattera1......
  • 一个可用于生产项目 基于 .NET 6 自研ORM
    FastFramework作者Mr-zhong代码改变世界....一、前言FastFramework基于NET6.0封装的轻量级ORM框架支持多种数据库SqlServerOracleMySqlPostgreSqlSqlite优点:体积小、可动态切换不同实现类库、原生支持微软特性、流畅API、使用简单、性能高、模型数据绑定采用......
  • 教我怎么用docker的network
    Docker提供了一种为容器创建和管理网络以相互通信的方法。创建Docker网络有多种方法,最简单的方法是使用dockernetworkcreate命令。此命令创建一个具有指定名称和驱动程序的新网络。驱动程序指定网络上的容器如何相互通信。最常见的驱动程序是桥接驱动程序,它用于为单个......
  • golang net/rpc inject data to service function
    在golang中,net/rpc库比较牛,只需要写函数,然后使用现成的ServerCodec就可以完成rpc服务了。但是有个问题,service函数的参数都是来自客户端的,如果服务器想为某个特殊的函数注入一些配置或状态参数,就不好弄了。解决方案:修改service函数,比如原来的参数是FuncArgs结构体,现在改成t......
  • 29、Pipeline Job进阶之部署应用至Kubernetes集群
    PipelineJob进阶之部署应用至Kubernetes集群在jenkins上的k8s云节点,在原来maven-and-docker模板的基础之上,添加容器也可以添加pod模板,通过继承的方式来实现maven-docker-kubectl方式来定义添加podtemplate添加容器:使用kubesphere/kubectl:latest镜像安装插件用于认证到k8s集群之......
  • k3s 基础 —— 配置 kubernetes-dashboard 的 token 过期时间
    拉取配置到本地:kubectlgetdeploykubernetes-dashboard-nkubernetes-dashboard-oyaml>dashboard-deploy.yaml增加参数:spec:containers:-args:---auto-generate-certificates---namespace=kubernetes-dashboard---to......
  • 快速部署Kubernetes 1.17:使用kubeadm轻松构建强大的容器编排平台
    在上一篇我们已经初步认识了Kubernetes,在本篇我们就开始着手搭建kubernetes,具体操作如下:1.环境准备(1).环境说明节点名称机器IP系统master10.2.3.191CentOS7.7node110.2.3.192CentOS7.7node210.2.3.190CentOS7.7(2).禁止swap(三台机器上都操作)swapoff-a#临时改配置文件/etc/f......