String轉成LambdaExpression(DynamicExpression)

有時一些簡單的流程控制,會把一些條件判斷寫在Database或config中,程式在執行時再剖析條件,來判斷流程是否可以進行,最早是自己寫剖析器然後反射取出值後在比對,因為自己功力有限,剖析的內容也有限,後來有嘗試使用CodeDOM,但使用上太麻煩,而且只為了短短的幾行,有殺雞用牛刀的感覺,後來.Net Framework3.5推出LambdaExpression後,這輕巧可以動態編譯產生Delegate,太適合用在這種情況,但是內建是沒有剖析器的,不過還好有現成的組件,可以輕鬆的將String轉成LambdaExpression。

有時一些簡單的流程控制,會把一些條件判斷寫在Database或config中,程式在執行時再剖析條件,來判斷流程是否可以進行,最早是自己寫剖析器然後反射取出值後在比對,因為自己功力有限,剖析的內容也有限,後來有嘗試使用CodeDOM,但使用上太麻煩,而且只為了短短的幾行,有殺雞用牛刀的感覺,後來.Net Framework3.5推出LambdaExpression後,這輕巧可以動態編譯產生Delegate,太適合用在這種情況,但是內建是沒有剖析器的,不過還好有現成的組件,可以輕鬆的將String轉成LambdaExpression。

 

  • Visual C# 2008 Samples中的DynamicExpression 在這個Sample中微軟寫了一隻剖析String成LambdaExpression的程式,位址在CSharpSamples\LinqSamples\DynamicQuery\Dynamic.cs,這一隻程式還擴展了IQuerable,詳情可參考這篇,因為常用到這一個檔案,我就在空間上放了一個,可直接下載

    NOTE:

    雖然在.Net Framework 4中有增加一個類別System.Linq.Expressions.DynamicExpression,但跟剖析String一點關係也沒有,明明這個Sample寫的很好,為什麼直接納入.Net Framework 4中呢?
    注:這裡的DynamicExpression類別命名空間為System.Linq.Dynamic。

  • lambda-parser  這是放在google code中的另一個將string轉成LambdaExpresson的套件,比DynamicExpression功能多一點,不過因為沒有常用,不知道詳情。

 

DynamicExpression的定義


{
    public static Type CreateClass(IEnumerable<DynamicProperty> properties);
    public static Type CreateClass(params DynamicProperty[] properties);
    public static Expression Parse(Type resultType, string expression, params object[] values);
    public static Expression<Func<T, S>> ParseLambda<T, S>(string expression, params object[] values);
    public static LambdaExpression ParseLambda(Type itType, Type resultType, string expression, params object[] values);
    public static LambdaExpression ParseLambda(ParameterExpression[] parameters, Type resultType, string expression, params object[] values);
}

 

範例


var lambda1 = System.Linq.Dynamic.DynamicExpression.ParseLambda(new ParameterExpression[]{},typeof(int), "(1+2+3+4+5+6-10)/2"); 
Console.WriteLine(lambda1.Compile().DynamicInvoke());//write 5        

//Sample 2    參數int 回傳int    
var lambda2 = System.Linq.Dynamic.DynamicExpression.ParseLambda(new ParameterExpression[]{Expression.Parameter(typeof(int),"i")},typeof(int), "(i*i*i)+1"); 
var func2 = lambda2.Compile();    
Console.WriteLine(func2.DynamicInvoke(2));//write 9
Console.WriteLine(func2.DynamicInvoke(4));//write 65
Console.WriteLine(func2.DynamicInvoke(-10));//write -999

//Sample 3 傳物件無具名參數 回傳屬性值
var lambda3 = System.Linq.Dynamic.DynamicExpression.ParseLambda<DateTime,object>("Date");     
Console.WriteLine(lambda3.Compile().DynamicInvoke(new DateTime(1984,3,1))); //write 1984-3-1 00:00:00

//Sample 4 傳物件具名參數 回傳操作後值
var lambda4 = System.Linq.Dynamic.DynamicExpression.ParseLambda(new ParameterExpression[]{Expression.Parameter(typeof(DateTime),"dd")},typeof(object),"dd.Date - dd.AddDays(-10)");     
Console.WriteLine(lambda4.Compile().DynamicInvoke(new DateTime(1984,3,1))); //write 10.00:00:00

//Sample 5 傳物件具名參數 回傳操作後值
var lambda5 = System.Linq.Dynamic.DynamicExpression.ParseLambda(new ParameterExpression[]{Expression.Parameter(typeof(DateTime),"dd")},typeof(object),"dd.Year - DateTime.Now.Year < 30 ? \"Young\" : \"Old Man\" ");     
Console.WriteLine(lambda5.Compile().DynamicInvoke(new DateTime(1984,3,1))); //write Young

 

雖然DynamicExpression很方便,但並竟只是片段文字的解析,沒有辦法作到CodeDom那麼全面,有以下的限制:

1. 不支援namespace, 如果Sample 5 改成 System.DateTime反而會失敗,因為會解析成System物件下沒有DateTime的欄位,預設的namespace是System,所以在System下的物件都可以在DynamicExpression中使用,因為不支援namespace所以也沒辦法呼叫其他物件下的Method,如System.Text.RegularExpressions.Regex.IsMatch就沒辦法呼叫。

2. 不支援ExtensionMethod,沒有強到可以解析ExtensionMethod。

3.必需是強型別,如Sample 2的參數改成object,會爆 object 無法乘於 object 錯誤(但我不知道VB.NET會不會有這問題)。

 

不過整體來說是非常方便的。