I want to setup Lazy Loading of registered types in a generic way that does not require me to register each one individually.
I can create an extension method to simultaneously register lazily like so:
public static IServiceCollection AddLazyTransient<T>(this IServiceCollection services)
where T : class
{
return services
.AddTransient<T>()
.AddTransient<Func<T>>(s => s.GetRequiredService<T>);
}
Which works fine, until unfortunately when my T
now a Type
, for example I am using Assembly style registration to get all classes in my WPF project which inherit from Window
like so:
public static IServiceCollection RegisterWindows<T>(this IServiceCollection services)
{
var assemblyName = typeof(T).Assembly.FullName ??
throw new ApplicationException(
$"No assembly name for type '{typeof(T).FullName}'");
var assembly = AppDomain.CurrentDomain.Load(assemblyName);
var windowTypes = assembly.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && t.IsAssignableTo(typeof(Window)))
.ToList();
foreach (var windowType in windowTypes)
{
services.AddTransient(windowType);
// But how do I register a Func<Type> here now?
}
return services;
}
I have tried the following:
var windowFuncType = Expression.GetFuncType(windowType);
services.AddTransient(windowFuncType, s => () => s.GetRequiredService(windowType));
But that gives the following error:
System.InvalidCastException: 'Unable to cast object of type System.Func'1[System.Object]' to type 'System.Func'1[MyApp.Wpf.MainWindow]'.'
Which makes sense. ServiceProvider.GetRequiredService(Type)
returns an object
, so I am registering a Func<object>
when it is expecting a Func<MainWindow>
So I need to convert Func<object>
to Func<Type>
manually via reflection, in the middle of the registration, how do I do that though?
CodePudding user response:
There are probably many ways to do this, but the simplest that comes to my mind is to just call the generic AddLazyTransient<T>
method using reflection from inside the non-generic AddLazyTransient
method. For instance:
public static void AddLazyTransient(this IServiceCollection services, Type type)
{
// Define an expression that contains a reference to the method to call
Expression<Action> expression = () => AddLazyTransient<object>(null);
// Extract the method from the expression
var addLazyTransientOfObject = ((MethodCallExpression)expression.Body).Method;
// Convert it to a generic type definition.
var addLazyTransientOfT = addLazyTransientOfObject.GetGenericMethodDefinition();
// Convert it to the exact closed method definition we require
var addLazyTransientOfType = addLazyTransientOfT.MakeGenericMethod(type);
// Call the method with its required parameter.
addLazyTransientOfType.Invoke(null, new[] { services });
}
There are certainly other ways of tackling this problem, such as calling AddTransient
by supplying a typeof(Func<>).MakeGenericType(type)
and a delegate that is compiled from the original Func<object>
with a cast, which can be done using Expression.Convert
, Expression.Invoke
, and Expression.Lambda.Compile()
, but as I said, I think this is easier.