【问题标题】:Mapping an interface method invocation to a remote instance method invocation via reflection通过反射将接口方法调用映射到远程实例方法调用
【发布时间】:2014-04-22 21:53:30
【问题描述】:

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

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&lt;T&gt;(以防操作返回值)。在后一种情况下,T 必须是可序列化的。
  • 每个方法参数都必须是可序列化的。
  • 代理使用的方法参数,即那些不会被传输的参数将被标记一个特殊的属性。

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

【问题讨论】:

  • 你好像在这里给自己挖了一个大坑。这些映射将放在哪里?用属性装饰你的服务器和/或客户端类可能是一种方法。如果我这样做,我想我会做一个 dsl 并描述所需的操作,然后在任一端发出正确的代码,即如果我想发生什么,通过一个通用描述耦合。例如让Thingy Blue。
  • @TonyHopkinson 这有点小漏洞,但应该可行.. 我认为。映射将位于“服务器”端。 “客户端”将仅发送接口方法的详细信息 + 那些未被属性标记为本地的参数的参数值。您能否对 DSL 方法更具体一点?
  • dsl 的理念是服务器和客户端有共同的语言。例如,客户端说“Add(1,2) 并且服务器解析该字符串并发出新的 DoStuff().Combine(1,2)。完全解耦,在任一端完成输入。您拥有的映射问题是服务器将拥有要知道客户端如何描述操作,而对于 dsl,任何一端都需要知道的是语言的语法和语义。
  • @TonyHopkinson 嗯,对。这就是界面的想法。我遇到这个问题的原因是我不控制操作,用户代码可以。我的代码只负责它的代理/网络/调用位。可用的操作由用户代码决定。在我的问题中,我可能应该更具体地说明这一点。我已经用附加信息更新了问题。
  • 这不是一个真正的问题,使用 dsl 意味着您的代理/调用代码将只是在服务器和客户端之间传递 dsl 代码。基本上只是直接的消息传递,也许还有一些路由。也就是说,切换到 dsl 基本上意味着在您的层中重新开始,并且在任一端都进行大量工作。

标签: c# reflection interface proxy invocation


【解决方案1】:

目前我已经通过接受铸造将成为这个问题的现实来解决这个问题。

接口已更改为类,因为每个接口实际上应该只有一个实现,并且通过删除输出的类型参数来简化方法。鉴于代码交易从表达式中提取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 之外,还需要提取实际的对象引用。这有点棘手,因为编译器会生成一个包含实际引用的类,但幸运的是有一个solution on StackOverflow

var methodCall = method.Body as MethodCallExpression; 如果(方法调用 == 空) { 抛出新的 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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-21
    • 2018-11-06
    • 2019-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-09
    相关资源
    最近更新 更多