Прежде чем я начну объяснять код, я сначала приведу свой пример использования, чтобы вы могли понять, что и почему происходит.
Предварительные условия:
- пусть будет сервер (Диспетчер очереди / Буфер в терминологии клиент-сервер)
- пусть будет один или несколько управляющих клиентов (Производитель в терминологии клиент-сервер)
- пусть будут клиенты (Потребители в терминологии клиент-сервер)
Рабочий процесс:
- управляющий клиент пишет скрипт C #, отправляет на сервер
- скрипт компилируется C # CodeDomProvider
- script can возвращает результат CallQueue или просто выполняет что-то на сервере
- , если происходит, сервер кэширует CallQueue
- , в то время как другие управляющие клиенты могут отправлять новые сценарии, которые обрабатываются
- какой-то клиент подключается к серверу и запрашивает CallQueue
- клиент получает CallQueue и выполняет его в указанное в нем время
Все в порядке, то этот момент, и работает безупречно.
Теперь техническая часть:
CallQueue - это класс, который использует лямбда-выражения в качестве входных данных для универсальных методов и хранит данные отражения, необходимые для выполнения вызовов вочередь на стороне клиента.
Почему вся эта сложность .. лямбда-генерики и т. д.?Тип Безопасность.Управлять клиентами глупо, и им нужно знать лишь несколько методов сценариев, а не настоящих программистов.Таким образом, отправка целого числа вместо строки или присвоение имени свойству с помощью опечатки может происходить часто.Вот почему сценарий использует лямбда-выражения и обобщения для ограничения того, что кто-то может напечатать.
Это компилируется на сервере и отклоняется, если не так.
Это символический сценарий, который управляющий клиент написал бы:
CallQueue cc = new CallQueue(new DateTime(2012,12,21,10,0,0));
// set property Firstname to "test person"
cc.AddPropertySet<Person, string>(x => x.FirstName, "test person");
// call method ChangeDescription with parameter "test order"
cc.AddVoidMethodCall<Order, string>(x => x.ChangeDescription, "test order");
// call method Utility.CreateGuid and send result to Person.PersonId
cc.AddFunctionCallWithDestinationPropertySet<Utility, Guid, Person>(src => src.CreateGuid, dst => dst.PersonId);
Что бы клиент получил, это экземпляр CallQueue и выполнить его так:
Order order = new Order();
Person person = new Person();
Utility util = new Utility();
CallQueue cc = /* already got from server */;
// when you call this execute the call queue will do the work
// on object instances sent inside the execute method
cc.Execute(new List<object> { order, person, util });
До сих пор все хорошо и безопасно, но есть некоторые последствия:
- клиент точно знает, какие объекты должны быть отправлены в метод execute, жестко запрограммированный в конструкции.
- управляющий клиент может написать сценарий, который работает с объектами, которые не будут отправляться, но все же компилироваться на сервере, посколькуТипы существуют
Возьмем, к примеру:
cc.AddFunctionCall<Int32, string>(x => x.ToString);
Это скомпилируется, но произойдет сбой, когда клиент выполнит его, поскольку он не отправляет Int32 в метод execute.
Хорошо, бла бла бла .... Итак, вопрос в следующем:
Как ограничить эти универсальные методы набором разрешенных типов, а не путем определения наследования:
where T : something
но больше похоже на
where listOftypes.Contains(T)
Или любое эквивалентное решение, которое ограничивает то, что может в него попасть ... Я не нашел для этого общего ограничения ...
Вот класс CallQueue:
[Serializable]
public class CallQueue : List<CallQueue.Call>
{
[Serializable]
public struct Call {
public MethodInfo Method;
public MethodInfo DestinationProperty;
public object[] Parameters;
public Call(MethodInfo m, MethodInfo d, object[] p) {
Method = m;
Parameters = p;
DestinationProperty = d;
}
}
public CallQueue(DateTime when) {
ScheduledTime = when;
}
public DateTime ScheduledTime
{
get;
set;
}
public void AddFunctionCall<TSrcClass, TResult>(Expression<Func<TSrcClass, Func<TResult>>> expr)
{
MethodResolver((LambdaExpression)expr, null, new object[] {});
}
public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TResult, TDest>(Expression<Func<TSrcClass, Func<TResult>>> expr, Expression<Func<TDest, TResult>> dest)
{
MethodResolver((LambdaExpression)expr, dest, new object[] { });
}
public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TParam1, TResult, TDest>(Expression<Func<TSrcClass, Func<TParam1, TResult>>> expr, TParam1 param, Expression<Func<TDest, TResult>> dest)
{
MethodResolver((LambdaExpression)expr, dest, new object[] { param });
}
public void AddFunctionCall<TSrcClass, TParam1, TResult>(Expression<Func<TSrcClass, Func<TParam1, TResult>>> expr, TParam1 param)
{
MethodResolver((LambdaExpression)expr, null, new object[] {param});
}
public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TParam1, TParam2, TResult, TDest>(Expression<Func<TSrcClass, Func<TParam1, TParam2, TResult>>> expr, TParam1 param, TParam2 param2, Expression<Func<TDest, TResult>> dest)
{
MethodResolver((LambdaExpression)expr, dest, new object[] { param, param2 });
}
public void AddFunctionCall<TSrcClass, TParam1, TParam2, TResult>(Expression<Func<TSrcClass, Func<TParam1, TParam2, TResult>>> expr, TParam1 param, TParam2 param2)
{
MethodResolver((LambdaExpression)expr, null, new object[] { param, param2 });
}
public void AddVoidMethodCall<TSrcClass, TParam>(Expression<Func<TSrcClass, Action<TParam>>> expr, TParam param)
{
MethodResolver((LambdaExpression)expr, null, new object[] { param });
}
public void AddVoidMethodCall<TSrcClass, TParam1, TParam2>(Expression<Func<TSrcClass, Action<TParam1, TParam2>>> expr, TParam1 param, TParam2 param2)
{
MethodResolver((LambdaExpression)expr, null, new object[] { param, param2 });
}
public void AddVoidMethodCall<TSrcClass, TParam1, TParam2, TParam3>(Expression<Func<TSrcClass, Action<TParam1, TParam2, TParam3>>> expr, TParam1 param, TParam2 param2, TParam3 param3)
{
MethodResolver((LambdaExpression)expr, null, new object[] { param, param2, param3 });
}
public void AddPropertySet<TSrcClass, TParam1>(Expression<Func<TSrcClass, TParam1>> expr, TParam1 param)
{
PropertyResolver((LambdaExpression)expr, new object[] {param});
}
public void Execute(List<object> instances) {
foreach (var call in this) {
var owner = instances.Find(o => o.GetType() == call.Method.DeclaringType);
if (call.DestinationProperty != null)
{
// execute method get result and set to destination property
object res = call.Method.Invoke(owner, call.Parameters);
var destOwner = instances.Find(o => o.GetType() == call.DestinationProperty.DeclaringType);
call.DestinationProperty.Invoke(destOwner, new object[] {res});
}
else
{
// just execute method
call.Method.Invoke(owner, call.Parameters);
}
}
}
private void MethodResolver(LambdaExpression expr, LambdaExpression dest, object[] param)
{
var body = (UnaryExpression)expr.Body;
var methodCall = (MethodCallExpression)body.Operand;
var constant = (ConstantExpression)methodCall.Arguments[2];
var method = (MethodInfo)constant.Value;
MethodInfo dmethod = null;
if (dest != null)
{
var prop = (MemberExpression)dest.Body;
var propMember = (PropertyInfo)prop.Member;
dmethod = propMember.GetSetMethod();
}
this.Add(new Call(method, dmethod, param));
Console.WriteLine(method.Name);
}
private void PropertyResolver(LambdaExpression expr, object[] param)
{
var prop = (MemberExpression)expr.Body;
var propMember = (PropertyInfo)prop.Member;
var method = propMember.GetSetMethod();
this.Add(new Call(method, null, param));
Console.WriteLine(method.Name);
}
}
Большое спасибо.Ура!