.NET coding patterns(davidfowl)
Table of contents
- Generics types as a factory
- Lazy initialization of services
- Single implementation multiple interfaces
- Creating instances of types from an IServiceProvider
- Caching singletons in generic types
- Resolving services when using IOptions<T>
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 FooAndBar
, IFoo
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 theSocket
in order to useCopyToAsync
, a helper that makes it easy to copy content from oneStream
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://files-cdn.cnblogs.com/files/thingk/%E5%BE%AE%E4%BF%A1%E6%94%B6%E6%AC%BE%E7%A0%81.bmp)