Problem: I want to write test for an application that has many static classes with static methods, so switching a often used class to dependency injection is impossible in one step.
So I want to preserve the static class, create a adapter class with an interface that calls the static methods, so I can use this interface step by step for dependency injection. (as explained here: https://stackoverflow.com/a/2416447/1453662)
But I don't want to write so many adapter classes for all the static classes so my question is if its possible to write a factory that will create a adapter class for a given interface type and a given target static class type e.g.:
// this is the problem
public static class CalculatorStatic {
public static int ComplexCalculation(int a, int b) {
return a + b;
}
}
// I will write this
public interface ICalculator {
int ComplexCalculation(int a, int b);
}
// I don't want to write this
public class CalculatorAdapter : ICalculator {
public int ComplexCalculation(int a, int b) {
return CalculatorStatic.ComplexCalculation(a, b);
}
}
// This should create all adapters for me
public class AdapterFactory {
public T CreateAdapter<T>(Type staticClassType) { // T is the InterfaceType
// Do some magic and return a dynamically created adapter
// that implements the interface and calls the static class
}
}
Instead of returning an interface I would suggest to return a delegate as an adapter.
public static TFunc CreateAdapter<TFunc>(Type staticClass, string methodName)
{
var method = staticClass.GetMethod(methodName,
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
var methodParameters = new ParameterExpression[parameterTypes.Length];
for (int i = 0; i < parameterTypes.Length; i++)
{
methodParameters[i] = Expression.Parameter(parameterTypes[i], "p" + i);
}
var lambda = Expression.Lambda<TFunc>(
Expression.Call(null, method, methodParameters), methodParameters);
return lambda.Compile();
}
And use it like this:
var adapter = CreateAdapter<Func<int, int, int>>(typeof(CalculatorStatic),
nameof(CalculatorStatic.ComplexCalculation));
Console.WriteLine(adapter(1, 2));
If you really-really want to use interfaces (because it has more than one method), you should create an adapter factory for each interface:
public ICalculator CreateAdapter(Type staticClassType)
{
return new CalculatorAdapter(staticClassType);
}
// todo: factory methods for other interfaces, too
And the calculator adapter:
private class CalculatorAdapter: ICalculator
{
private readonly Func<int, int, int> complexCalculationAdapter;
internal CalculatorAdapter(Type staticClassType)
{
complexCalculationAdapter = CreateAdapter<Func<int, int, int>>(staticClassType,
nameof(ICalculator.ComplexCalculation));
// TODO: initialize the other fields if there are more interface methods
}
public int ComplexCalculation(int a, int b)
{
return complexCalculationAdapter(a, b);
}
}
Update
If you really want to create a single method for all interfaces, you should generate a dynamic class.
Please note that this example is not perfect. You should cache the dynamic assembly and module instead of always creating a new one, and if you call ref/out arguments, you should assign them back, etc. But maybe the intention is clear. Tip for the code: compile a code which implements an interface directly and disassemble it to see what code to emit in the generator.
public static T CreateAdapter<T>(Type staticClassType)
{
AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(typeof(T).Name + "Adapter"),
AssemblyBuilderAccess.RunAndSave);
ModuleBuilder mb = ab.DefineDynamicModule(typeof(T).Name + "Adapter.dll");
// public class TAdapter : T
TypeBuilder tb = mb.DefineType(typeof(T).Name + "Adapter", TypeAttributes.Public | TypeAttributes.Class,
typeof(object), new Type[] { typeof(T) });
// creating methods
foreach (var methodInfo in typeof(T).GetMethods())
{
var parameters = methodInfo.GetParameters();
var parameterTypes = parameters.Select(p => p.ParameterType).ToArray();
var method = tb.DefineMethod(methodInfo.Name,
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot,
methodInfo.ReturnType, parameterTypes);
// adding parameters
for (int i = 0; i <parameters.Length; i++)
{
method.DefineParameter(i + 1, parameters[i].Attributes, parameters[i].Name);
}
// calling the static method from the body and returning its result
var staticMethod = staticClassType.GetMethod(methodInfo.Name, parameterTypes);
var code = method.GetILGenerator();
for (int i = 0; i < parameters.Length; i++)
{
code.Emit(OpCodes.Ldarg_S, i + 1);
}
code.Emit(OpCodes.Call, staticMethod);
code.Emit(OpCodes.Ret);
}
return (T)Activator.CreateInstance(tb.CreateType());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With