3

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
     }
}
Community
  • 1
  • 1
jansepke
  • 1,933
  • 2
  • 18
  • 30
  • Have you considered code generation? Sounds like the perfect solution to me - it's very easy (using either reflection or Roslyn), and it maintains compile-time checking and full IntelliSense support. – Luaan Feb 11 '16 at 09:41
  • yeah I'm currently looking at PostSharp, for type generation. I don't really need the compile-time checking for the adapter class, because I will only use reference the interface. maybe Castle Dynamic Proxy could be a solution, but I'm open for anything that will work – jansepke Feb 11 '16 at 11:31

1 Answers1

2

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

}

jansepke
  • 1,933
  • 2
  • 18
  • 30
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • Nice answer, but I think the OP explicitly wants to use interfaces, so that he can replace the static method calls with implementations of this interfaces and use DI in the long term to improve the object oriented design. – M.E. Feb 11 '16 at 10:41
  • Ok, I edited the answer. Still not as general as OP wants but now it is enough to implement one factory per interface. – György Kőszeg Feb 11 '16 at 10:46
  • nice answer, but I want to use an interface and only one adapter for all interfaces, to not write two additional files per static class – jansepke Feb 11 '16 at 11:29
  • @JanS: No, you don't need to write two additional files per static class! As long as they "implement" the same interface, you can use the same adapter. You need to write a new adapter only for a new interface. – György Kőszeg Feb 11 '16 at 11:39
  • @taffer but each static class has different methods and need its own interface – jansepke Feb 11 '16 at 12:05
  • I updated the answer. Use it carefully, it should be still fine-tuned but you can see the way now. :) – György Kőszeg Feb 11 '16 at 13:04
  • @taffer I was creating a similar approach but you were faster so i dont post it. I think you should be a bit more explicit about the method you are searching in the static class, since method overloads are very common. Its not perfect but should handle more cases, have a look at this: http://pastebin.com/0tc48cxt. – thehennyy Feb 11 '16 at 13:48
  • @thehennyy: You are right, one should use the `GetMethod(string, Type[])` overload. And it should be checked, whether it really exists, has the same amount of parameters with the same types as the interface, etc. As I have written, it has to be still fine-tuned, this is just a proof-of-concept demo. – György Kőszeg Feb 11 '16 at 13:56