该系统由一组对等连接组成。每个对等点都提供一组它可以执行的“操作”。每个动作都由接口上的一个方法表示,比如
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
或其他方式 - 用户不必在太多的角落里挣扎。
编辑
实际的动作签名 (ie IMyCoolActions
) 和动作的实现 (ie DoStuff
) 由我的代码的用户控制。我的代码只负责代理的生成、调用数据的传输和正确操作方法的调用。
目前对签名的要求是:
- 签名是通过派生自我的操作接口之一的接口定义的。
- 每个接口可能只有方法,所以没有属性或事件。
- 每个方法都必须返回一个
Task
(如果动作没有返回值)或Task<T>
(如果动作确实返回值)。在后一种情况下T
必须是可序列化的。 - 每个方法参数都必须是可序列化的。
- 代理使用的方法参数,即那些不会被传输的方法参数将被标记一个特殊的属性。
动作实现有类似(但不相同)的要求。