通过反射将接口方法调用映射到远程实例方法调用

本文关键字:调用 映射 实例方法 方法 反射 接口 | 更新日期: 2023-09-27 17:59:21

系统由一组对等连接组成。每个对等方都提供了一组可以执行的"操作"。每个动作都由接口上的一个方法表示,比如

public interface IMyCoolActions
{
    int Add(int first, int second);
}

"客户端"对等端将为操作接口创建一个代理对象,以便它可以调用该接口上的方法。当调用此代理上的某个接口方法时,代理会收集参数数据,对其进行打包,并通过网络将其发送到"服务器"对等端。"服务器"对等端对数据进行解包,确定调用了哪个方法并调用该方法,即基本上是RPC方法

现在,"服务器"对等端不必具有IMyCoolActions接口的实际实现。它所需要的只是一种方法:

  • 具有相同的参数
  • 具有相同的退货类型
  • 执行调用的接口方法指示的操作

因此,它可能有以下类的实例

public sealed class DoStuff
{
    public int Combine(int first, int second)
    {
        return first + second;
    }
}

显然,需要将IMyCoolActions.Add方法映射到DoStuff.Combine方法的映射。简单的方法是使DoStuff实现IMyCoolActions接口,但目标是断开这两者的连接,以便允许调用方提供仅在本地端使用的参数。例如,以下内容应该仍然是可映射的

public interface IMyCoolActions
{
    Task<int> Add(int first, int second, [ConnectionTimeoutAttribute]TimeSpan timeout);
}
public sealed class DoStuff
{
    public int Combine([RemoteIdAttribute]IPEndpoint origin, int first, int second)
    {
        return IsAllowedToCommunicate(orgin) ? first + second : int.MaxValue;
    }
}

这种映射应该仍然有效,因为客户端在本地使用超时值(作为..井超时),并且在网络数据解包时向服务器提供原始IP数据。

除了生成映射之外,整个系统都已实现。到目前为止,找到一种合适的方法来创建正确的映射是不切实际的。我尝试了以下方法(及其衍生物):

public interface ICommandMapper<TCommand>
{
    IMethodWithoutResultMapper ForMethodWithResult<T1, T2, T3, TOut>(
        Expression<Func<TCommand, T1, T2, T3, Task<TOut>>> methodCall);
}
public interface IMethodWithResultMapper
{
    void ToMethod<TInstance, T1, T2, T3, TOut>(
        TInstance instance,
        Expression<Func<TInstance, T1, T2, T3, TOut>> methodCall);
}

然后可以通过以下方式调用:

var instance = new DoStuff();
ICommandMapper<IMyCoolActions> map = CreateMap();
map.ForMethodWithoutResult((command, first, second, timeout) => command.Add(first, second, timeout))
    .ToMethod(instance, (ipaddress, first, second) => instance.Combine(ipaddress, first, second));

不幸的是,C#编译器无法推断不同的类型。虽然缺乏类型推理是可以解决的,但它会导致大量难看的类型转换和类型指定。

所以我想要的是一个在这些方法之间映射的建议/想法,这样

  • 可以确定使用哪种接口方法和哪种对象方法(通过使用反射、DynamicObject或其他方式
  • 用户不必在太多的角落里争吵

编辑

实际动作签名(即IMyCoolActions)和动作的实现(即DoStuff)由我的代码的用户控制。我的代码只负责生成代理、传输调用数据和调用正确的操作方法。

目前对签名的要求是:

  • 签名是通过一个接口定义的,该接口派生自我的一个操作接口
  • 每个接口可能只有方法,因此没有属性或事件
  • 每个方法都必须返回一个Task(如果操作没有返回值)或Task<T>(如果操作返回值)。在后一种情况下,T必须是可序列化的
  • 每个方法参数都必须是可序列化的
  • 代理使用的方法参数,即不会传输的方法参数将用特殊属性标记

对于操作实现有类似(但不完全相同)的要求。

通过反射将接口方法调用映射到远程实例方法调用

目前我已经解决了这个问题,因为我接受了这个问题的现实。

接口已经改为类,因为每个接口实际上应该只有一个实现,并且通过删除输出的类型参数简化了方法。假设代码处理从表达式中提取MethodInfo,则仍然可以获得返回类型,而无需定义多个方法重载即可在方法签名中具有返回类型。

public sealed class CommandMapper<TCommand>
{
    public MethodMapper For<T1, T2, T3>(Expression<Action<TCommand, T1, T2, T3>> methodCall)
    {
        return CreateMethodMapper(methodCall);
    }
}
public sealed class MethodMapper
{
    public void To<T1, T2, T3>(Expression<Action<T1, T2, T3>> methodCall)
    {
        // Do stuff
    }
}

有了这个界面,用户调用这样的方法:

var map = CommandMapper<IMyCoolActions>.CreateMap();
map.For<int, int, TimeSpan>((command, first, second, timeout) => command.Add(first, second, timeout))
    .To((IPEndpoint ipaddress, int first, int second) => instance.Combine(ipaddress, first, second));

CommandMapper中,MethodInfo通过以下方式获得:

var methodCall = method.Body as MethodCallExpression;
if (methodCall == null)
{
    throw new InvalidCommandMethodExpressionException();
}
return methodCall.Method;

在CCD_ 15中,除了CCD_ 16之外,还需要提取实际的对象参考。这稍微有点棘手,因为编译器生成了一个包含实际引用的类,但幸运的是,StackOverflow上有一个解决方案。

var methodCall=方法。Body as MethodCallExpression;if(methodCall==null){抛出新的InvalidCommandMethodExpressionException();}

var methodInfo = methodCall.Method;
// if the object on which the method is called is null then it's a static method
object instance = null;
if (methodCall.Object != null)
{
    var member = methodCall.Object as MemberExpression;
    if (member == null)
    {
        throw new InvalidCommandMethodExpressionException();
    }
    // The member expression contains an instance of an anonymous class that defines the member
    var constant = member.Expression as ConstantExpression;
    if (constant == null)
    {
        throw new InvalidCommandMethodExpressionException();
    }
    var anonymousClassInstance = constant.Value;
    // The member of the class
    var calledClassField = member.Member as FieldInfo;
    // Get the field value
    instance = calledClassField.GetValue(anonymousClassInstance);
}
return new Tuple<object, MethodInfo>(instance, methodInfo);