繁体   English   中英

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

[英]Mapping an interface method invocation to a remote instance method invocation via reflection

该系统由一组对等连接组成。 每个对等方都提供一组可以执行的“动作”。 每个动作都由接口上的方法表示,例如

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必须是可序列化的。
  • 每个方法参数必须可序列化。
  • 代理使用的方法参数,即那些将不被传输的方法参数,将被标记为特殊属性。

动作实现有相似(但不相同)的要求。

目前,我已经接受了铸造将是解决该问题的事实,从而解决了该问题。

接口已更改为类,因为每个接口实际上仅应有一个实现,并且通过删除输出的type参数简化了方法。 鉴于代码交易从表达式中提取了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;

MethodMapper除了MethodInfo之外,还需要提取实际的对象引用。 这有点棘手,因为编译器会生成一个包含实际引用的类,但幸运的是,StackOverflow上有一个解决方案

var methodCall = method.Body as MethodCallExpression; 如果(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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM