The system consists of a set of peer to peer connections. Each peer provides a set of 'actions' that it can perform. Each action is represented by a method on an interface, say
public interface IMyCoolActions
{
int Add(int first, int second);
}
The 'client' peer will create a proxy object for the action interface so that it can invoke the methods on this interface. When one of the interface methods on this proxy is called, the proxy gathers the parameter data, packages it and sends it across the network to the 'server' peer. The 'sever' peer unpacks the data, determines which method was invoked and calls the method, i.e. basically an RPC approach
Now the 'server' peer does not have to have an actual implementation of the IMyCoolActions
interface. All it needs is a method that will:
So it could have an instance of the following class
public sealed class DoStuff
{
public int Combine(int first, int second)
{
return first + second;
}
}
Obviously there will need to be a mapping that maps the IMyCoolActions.Add
method to the DoStuff.Combine
method. The easy way would be to make DoStuff
implement the IMyCoolActions
interface, however the goal is to disconnect these two so that it is possible to allow the callers to provide parameters that are only used on the local end. e.g. the following should still be mappable
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;
}
}
This mapping should still work because the client uses the timeout value locally (as a .. well time-out) and the server is provided with the origin IP data when the network data is unpacked.
The entire system has been implemented except for the generation of the mapping. It has so far proven illusive to find an appropriate way to create the correct mapping. I have tried the following approach (and derivatives of it):
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);
}
Which can then called in the following way:
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));
Unfortunately the C# compiler is not able to infer the different types. While the lack of type inference is solvable it leads to a lot of ugly casting and type specifying.
So what I want is a suggestion / idea for mapping between these methods so that
DynamicObject
or other meansEDIT
The actual action signatures (i.e. IMyCoolActions
) and the implementation of the action (i.e. DoStuff
) are in the control of the users of my code. My code is only responsible for the generation of the proxy, transporting of the call data and invocation of the correct action method.
The current demands on signatures are:
Task
(in case the action doesn't return a value) or Task<T>
(in case the action does return a value). In the latter case T
must be serializable.There are similar (but not identical) requirements for the action implementations.
For the moment I have solve the issue by accepting that casting will be a fact of life for this problem.
The interfaces have been changed to classes because there should really only be one implementation of each and the methods have been simplified by removing the type parameter for the output. Given that the code deals extracts the MethodInfo
from the expression it will still be possible to get the return types without having to define multiple method overloads to be able to have the return type in the method signature.
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
}
}
With this interface the user calls the methods like this:
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));
In the CommandMapper
the MethodInfo
is obtained by:
var methodCall = method.Body as MethodCallExpression;
if (methodCall == null)
{
throw new InvalidCommandMethodExpressionException();
}
return methodCall.Method;
In the MethodMapper
besides the MethodInfo
the actual object reference needs to be extracted as well. This is slightly more tricky because the compiler generates a class that holds the actual reference, but fortunately there was a solution on StackOverflow.
var methodCall = method.Body as MethodCallExpression; if (methodCall == null) { throw new 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);