Last active
May 2, 2023 11:24
-
-
Save noseratio/239918e05697fb5952865dd19a2ae3aa to your computer and use it in GitHub Desktop.
A factory for ActivatorUtilities.CreateInstance
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://stackoverflow.com/q/69207420/1768303 | |
#nullable enable | |
using System; | |
using System.Diagnostics; | |
using System.Linq; | |
using Microsoft.Extensions.DependencyInjection; | |
namespace App | |
{ | |
static class Program | |
{ | |
private static IServiceProvider BuildServices() => new ServiceCollection() | |
.AddFactory<ISomething, Something>() | |
.BuildServiceProvider(); | |
static void Main() | |
{ | |
var serviceProvider = BuildServices(); | |
var something = serviceProvider.GetRequiredService<ISomething>(); | |
serviceProvider.Inject<ISomething>(); | |
serviceProvider.Inject<ISomething>(Guid.NewGuid()); | |
serviceProvider.Inject<ISomething>(something); | |
using var scope = serviceProvider.CreateScope(); | |
scope.ServiceProvider.Inject<ISomething>(Guid.NewGuid()); | |
serviceProvider.GetRequiredService<IFactory<ISomething>>().CreateInstance(String.Empty); | |
} | |
} | |
/// <summary> | |
/// A sample interface for DI | |
/// </summary> | |
public interface ISomething | |
{ | |
void DoSomething() => Console.WriteLine(nameof(DoSomething)); | |
} | |
public class Something : ISomething | |
{ | |
public Something() => | |
Console.WriteLine($"{this.GetType()} created"); | |
public Something(Guid context) => | |
Console.WriteLine($"{this.GetType()} created with {context.GetType()}"); | |
public Something(String context) => | |
Console.WriteLine($"{this.GetType()} created with {context.GetType()}"); | |
public Something(ISomething source) => | |
Console.WriteLine($"{this.GetType()} created with {source.GetType()}"); | |
public Something(IServiceProvider sp) => | |
Console.WriteLine($"{this.GetType()} created with {sp.GetType()}"); | |
public Something(IServiceProvider sp, Guid context) => | |
Console.WriteLine($"{this.GetType()} created with {sp.GetType()}, {context.GetType()}"); | |
public Something(IServiceProvider sp, ISomething context) => | |
Console.WriteLine($"{this.GetType()} created with {sp.GetType()}, {context.GetType()}"); | |
} | |
public interface IFactory<TService> | |
where TService : class | |
{ | |
IServiceProvider ServiceProvider { get; } | |
TService CreateInstance(params object[] parameters); | |
TService Inject(params object[] parameters); | |
} | |
/// <summary> | |
/// DependencyInjectionExtensions | |
/// </summary> | |
public static class DependencyInjectionExtensions | |
{ | |
private class Factory<TService, TInstance> : | |
IFactory<TService> | |
where TService : class | |
where TInstance : class, TService | |
{ | |
public Factory(IServiceProvider serviceProvider) => | |
this.ServiceProvider = serviceProvider; | |
public IServiceProvider ServiceProvider { get; } | |
public TService CreateInstance(params object[] parameters) => | |
Activator.CreateInstance(typeof(TInstance), parameters) as TService ?? | |
throw new InvalidCastException(); | |
public TService Inject(params object[] parameters) | |
{ | |
// is IServiceProvider supplied as any of params | |
if (!parameters.Any(p => p is IServiceProvider)) | |
{ | |
// if not, do we have a public constructor compatible with params + IServiceProvider? | |
var paramTypes = parameters.Select(p => p.GetType()).Append(typeof(IServiceProvider)); | |
if (HasSuitableConstructor(paramTypes)) | |
{ | |
// if so, add the current IServiceProvider to the DI param list and create an instance | |
return ActivatorUtilities.CreateInstance<TInstance>( | |
this.ServiceProvider, parameters.Append(this.ServiceProvider).ToArray()); | |
} | |
} | |
// otherwise, use the default behavior | |
return ActivatorUtilities.CreateInstance<TInstance>(this.ServiceProvider, parameters); | |
} | |
private static bool HasSuitableConstructor(IEnumerable<Type> paramTypes) | |
{ | |
// go through all constructors | |
foreach (var c in typeof(TInstance) | |
.GetConstructors() | |
.Where(c => c.IsPublic)) | |
{ | |
// try to find a constructor that has matching parameters for paramTypes | |
var matched = true; | |
foreach (var paramType in paramTypes) | |
{ | |
if (c.GetParameters() | |
.Select(p => p.ParameterType) | |
.Any(p => p.IsAssignableFrom(paramType)) == false) | |
{ | |
matched = false; | |
break; | |
} | |
} | |
if (matched) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
} | |
/// <summary> | |
/// Add a factory for parametrized DI injections | |
/// </summary> | |
public static IServiceCollection AddFactory<TService, TInstance>( | |
this IServiceCollection @this) | |
where TService : class | |
where TInstance : class, TService | |
{ | |
return @this | |
.AddSingleton<IFactory<TService>>(serviceProvider => | |
new Factory<TService, TInstance>(serviceProvider)) | |
.AddTransient<TService>(serviceProvider => | |
serviceProvider.GetRequiredService<IFactory<TService>>().Inject()); | |
} | |
/// <summary> | |
/// Inject with parameters | |
/// </summary> | |
public static TService Inject<TService>( | |
this IServiceProvider @this, params object[] parameters) | |
where TService : class | |
{ | |
return @this.GetRequiredService<IFactory<TService>>().Inject(parameters); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment