【问题标题】:Dynamic adapter creation for static class from interface从接口为静态类创建动态适配器
【发布时间】:2016-05-21 22:56:33
【问题描述】:

问题:我想为一个有很多带有静态方法的静态类的应用程序编写测试,因此不可能一步将常用的类切换到依赖注入。

所以我想保留静态类,创建一个带有调用静态方法的接口的适配器类,这样我就可以逐步使用这个接口进行依赖注入。 (这里解释: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
     }
}

【问题讨论】:

  • 你考虑过代码生成吗?听起来对我来说是一个完美的解决方案 - 它非常简单(使用反射或 Roslyn),并且它保持编译时检查和完整的 IntelliSense 支持。
  • 是的,我目前正在研究 PostSharp,用于类型生成。我真的不需要适配器类的编译时检查,因为我只会使用引用接口。也许 Castle Dynamic Proxy 可能是一个解决方案,但我愿意接受任何可行的方法

标签: c# reflection


【解决方案1】:

我建议不要返回接口,而是将委托作为适配器返回。

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

}

【讨论】:

  • 不错的答案,但我认为 OP 明确希望使用接口,以便他可以用该接口的实现替换静态方法调用,并长期使用 DI 来改进面向对象的设计。
  • 好的,我编辑了答案。仍然没有 OP 想要的那么通用,但现在每个接口实现一个工厂就足够了。
  • 不错的答案,但我想为所有接口使用一个接口和一个适配器,而不是为每个静态类编写两个额外的文件
  • @JanS:不,您不需要为每个静态类编写两个额外的文件!只要它们“实现”相同的接口,就可以使用相同的适配器。您只需为新接口编写新适配器。
  • @taffer 但是每个静态类都有不同的方法,需要自己的接口
猜你喜欢
  • 2018-07-03
  • 2016-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-17
相关资源
最近更新 更多