2011-04-29 105 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.