Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic adapter creation for static class from interface

Tags:

c#

reflection

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
     }
}
like image 285
Jan S Avatar asked Apr 26 '26 04:04

Jan S


1 Answers

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

}

like image 132
György Kőszeg Avatar answered Apr 27 '26 21:04

György Kőszeg



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!