系统由一组对等连接组成。每个对等体提供一组可以执行的“动作”。每个动作都由界面上的方法表示,比如说
public interface IMyCoolActions
{
int Add(int first, int second);
}
'client'对等体将为动作界面创建一个代理对象,以便它可以调用此接口上的方法。当调用此代理上的某个接口方法时,代理会收集参数数据,将其打包并通过网络将其发送到“服务器”对等方。 'sever'对等体解包数据,确定调用哪个方法并调用方法,即基本上是RPC方法
现在,'server'对等体不必具有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;
}
}
此映射应该仍然有效,因为客户端在本地使用超时值(作为... well time-out),并且在解压缩网络数据时为服务器提供源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
必须是可序列化的。动作实施有类似(但不完全相同)的要求。
答案 0 :(得分:0)
目前,我已经通过接受铸造将解决这个问题来解决这个问题。
接口已更改为类,因为实际上只应有一个实现,并且通过删除输出的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
,还需要提取实际的对象引用。这稍微有些棘手,因为编译器会生成一个包含实际引用的类,但幸运的是有一个solution on StackOverflow。
var methodCall = method.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);