2011-04-29 47 views
1

在我开始解释代码之前,我会首先给出我的用例,以便您能够理解发生了什么以及为什么发生。泛型,Lambda和反射问题[复杂]

先决条件:

  • 让有一个服务器(队列调度器/缓冲在客户端服务器术语)
  • 让有一个或多个管理客户端(客户端服务器术语监制)
  • 让有是客户端(客户端服务器术语消费者)

工作流程:

  • 管理客户端写了一个C#脚本,向服务器发送
  • 脚本得到由C#CodeDomProvider编译
  • 脚本可以给回一个CallQueue结果,或
  • 如果发生服务器缓存CallQueue只执行服务器上的东西
  • 在此期间其他管理客户端可以发送被处理
  • 一些客户端连接到服务器并请求一个CallQueue
  • CLIEN新脚本t获取CallQueue,并在指定的时间执行它

一切都很好,直到这一点,并完美地工作。

现在的技术部分:

的CallQueue是使用lambda表达式作为通用方法输入一个类,并且执行在客户端上的队列中的呼叫所需存储反射数据。

为什么所有这些复杂.. lambda泛型等?类型安全。 管理客户端是愚蠢的,只需要知道一些写脚本的方法,而不是真正的程序员。所以发送一个整数而不是一个字符串或命名一个打印错误的属性可能会经常发生。这就是为什么脚本使用lambdas和泛型来限制某人可以输入的内容。

这会在服务器上编译并在错误的情况下被拒绝。

这是一个管理客户端会写一个象征性的脚本:

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 }); 

了这里的一切是罚款和类型安全,但有暗示:

  • 客户端完全知道哪些对象必须发送到e xecute方法,通过设计硬编码
  • 管理客户端可以编写脚本,就不会被发送到,但仍然在服务器上编译,因为类型存在

就拿对象进行操作:

cc.AddFunctionCall<Int32, string>(x => x.ToString); 

这将编译,但在客户端执行时会失败,因为它不会将Int32发送到execute方法。

好了,唧唧歪歪.... 所以,问题是:

如何限制一组允许的类型的通用方法 - 而不是通过定义继承:

where T : something 

但更像

where listOftypes.Contains(T) 

或者说限制什么可以进入任何的同类解决方案...... 我没有找到这个通用contraint ...

这里是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); 
     } 
    } 

非常感谢你。 干杯!

回答

2

提供的参数 派生类型参数因此有两个事情可以做。一个是确保listOftypes中的所有类型都来自相同的基类,或者实现相同的接口,在这种情况下,您可以使用where。

鉴于您的问题似乎表明这不是您想要的,您可以通过查看typeof(T)并查看该类型是否包含在listOfTypes中,以便在运行时获得更好的错误报告。不如你想要的那么好,但你是有限的。

+0

实际上界面的东西是可以工作的......当我想到这个时,我想......我不能将继承和其他方法添加到类中,但现在我想到了它......我可以使用一个EMPTY接口?没有继承,没有附加的方法...我必须先看看是否有部署的影响,但这可以工作... – 2011-04-29 01:46:56

+0

是的,我可以走了,感谢提醒我再想想:) – 2011-04-30 14:40:38

1

你不能做你的要求,如:

where listOftypes.Contains(T) 

但是你可以申请多种类型,但你需要给他们打出来,而不是保持一个集合,如您建议。

There are predefined ways to apply constraints

其中T:结构

类型参数必须是一个值 类型。可以指定除Nullable 之外的任何值类型。有关更多 信息,请参见使用空值 类型(C#编程指南)。

其中T:类

类型参数必须是引用 类型;这也适用于任何类, 接口,委托或数组类型。

其中T:新的()

类型参数必须有一个公共 参数构造函数。当使用 以及其他约束条件时,必须先指定 last()约束条件。

其中T:

类型参数必须是或从指定的基类派生 。

其中T:

类型参数必须是或实现 指定接口。指定多个 接口约束可以是 。约束接口 也可以是通用的。

其中T:U

对于T提供必须 是或从U.