从接口创建静态类的动态适配器

时间:2016-02-11 08:55:53

标签: c# reflection

问题:我想为具有静态方法的许多静态类的应用程序编写测试,因此无法一步将常用类切换为依赖注入。

所以我想保留静态类,用一个调用静态方法的接口创建一个适配器类,所以我可以逐步使用这个接口来进行依赖注入。 (如下所述:https://stackoverflow.com/a/2416447/1453662

但是我不想为所有静态类编写这么多的适配器类,所以我的问题是它是否可以编写一个工厂来为给定的接口类型和给定的目标静态类创建一个适配器类输入例如:

// 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
     }
}

1 个答案:

答案 0 :(得分:2)

我建议将代理作为适配器返回,而不是返回接口。

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

并像这样使用它:

var adapter = CreateAdapter<Func<int, int, int>>(typeof(CalculatorStatic),
    nameof(CalculatorStatic.ComplexCalculation));
Console.WriteLine(adapter(1, 2));

如果你真的真的想要使用接口(因为它有多个方法),你应该为每个接口创建一个适配器工厂:

public ICalculator CreateAdapter(Type staticClassType)
{
    return new CalculatorAdapter(staticClassType);
}

// todo: factory methods for other interfaces, too

计算器适配器:

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

<强>更新

如果您真的想为所有接口创建单个方法,则应生成动态类。

请注意,此示例并不完美。你应该缓存动态程序集和模块,而不是总是创建一个新的,如果你调用ref / out参数,你应该将它们分配回来,等等。但是意图可能很明确。代码提示:编译直接实现接口的代码并反汇编它以查看在生成器中发出的代码。

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

}