javascript
表达式求值Spring.Expressions
?
?簡介
Spring.Expressions命名空間可以用一種強大的表達式語言在運行時操作對象。這種語言可以讀寫屬性值、調用方法、訪問數組/集合/索引器的元素、進行算術和邏輯運算,同時支持命名變量,并且能夠通過名稱從IoC容器獲取對象。
在Spring.NET中,該命名空間是其它許多功能(比如IoC容器中的增強屬性求值、數據驗證框架及ASP.NET的數據綁定框架)的基礎。如果需要以對象的運行時狀態為依據進行求值,您還會發現一些更加有趣的特性。對于有Java背景的開發人員來說,Spring.Expressions命名空間的功能和Java的OGNL(Object Graph Navigation Language)很相似。
本章將用一個Inventor類及與其相關的類型為例來講解表達式語言。這些類的代碼和使用到的數據都列舉在本章的最后一節本章所用的示例類型中。這些類型是從Spring.Expression命名空間的NUnit測試項目中拿出來的,您可以參考該項目的源碼來學習表達式的其它用法。
表達式求值
Spring.Expressions命名空間的核心類是ExpressionEvaluator,其主要方法如下:
public static object GetValue(object root, string expression);public static object GetValue(object root, string expression, IDictionary variables)public static void SetValue(object root, string expression, object newValue)public static void SetValue(object root, string expression, IDictionary variables, object newValue)其中,參數root就是我們要在其上進行求值的目標“根”對象,參數expression是用于求值的字符串表達式。其余參數用來指定在表達式中使用的變量,稍后再討論它們。下面的代碼從Inventor實例中讀取屬性值。Invertor類的代碼可以參見本章所用的示范類型。
Inventor tesla = new Inventor("Nikola Tesla", new DateTime(1856, 7, 9), "Serbian");tesla.PlaceOfBirth.City = "Smiljan";string evaluatedName = (string) ExpressionEvaluator.GetValue(tesla, "Name"); string evaluatedCity = (string) ExpressionEvaluator.GetValue(tesla, "PlaceOfBirth.City"));上面的代碼執行后,變量evalutedName的值為"Nikola Tesla",evalutedCity的值為"Smijan"。在表示嵌套屬性時要用小數點分隔路徑。設置屬性值與讀取屬性值的用法類似,如果我們要改寫歷史,將Tesla的出生地改為其它城市,可以用下面的代碼:
ExpressionEvaluator.SetValue(tesla, "PlaceOfBirth.City", "Novi Sad");Spring.Expressions命名空間中另有一個不太常用到的類:Expression。如果要用某個表達式針對同一對象進行多次求值,該類可以提高性能。ExpressionEvaluator類會在每次調用GetValue或SetValue時解析表達式;而Expression則會將解析和反射的結果緩存起來。該類的部分方法如下(按:方法很多,參見API文檔):
public static IExpression Parse(string expression)public override object Get(object context, IDictionary variables)public override void Set(object context, IDictionary variables, object newValue)可以用下面的代碼讀取前面例子的屬性值:
IExpression exp = Expression.Parse("Name");string evaluatedName = (string) exp.GetValue(tesla, null);在使用ExpressionEvaluator的時候,會涉及到幾個異常類型。如果引用的屬性不存在,會拋出InvalidPropertyException;如果在遍歷嵌套屬性的路徑時遇到了null值,會拋出NullValueInNextedPathException(按:對于嵌套的屬性,除了最末一個點后的實際屬性,路徑當中的值都必須不能為空);如果傳值時發生其它錯誤,會拋出ArgumentException和NotSupportedException異常。
表達式語言有自己的語法,并且使用ANTLR來構建語法分析器和解析器,語法錯誤在解析時就會被捕獲。如果想深入了解解析器的實現細節,可以參考源文件中的語法文件Expression.g。另外,目前Spring.NET使用的ANTLR.DLL程序集是用Spring.NET的key標記的。將來ANTLR會在新版本中使用自己的強命名程序集。
?語言參考
文字表達式
文字表達式可以表示字符串、日期、數字值(int、real和hex)、布爾及空值。字符串以單引號包圍。表達式內的單引號要用反斜線進行轉義。下面是文字表達式的一些簡單用法。注意文字表達式一般不會這樣單獨使用,多是作為復雜表達式的一部分,比如作為邏輯比較操作符的一個操作數。
string helloWorld = (string) ExpressionEvaluator.GetValue(null, "'Hello World'"); // evals to "Hello World"string tonyPizza = (string) ExpressionEvaluator.GetValue(null, "'Tony\\'s Pizza'"); // evals to "Tony's Pizza"double avogadrosNumber = (double) ExpressionEvaluator.GetValue(null, "6.0221415E+23");int maxValue = (int) ExpressionEvaluator.GetValue(null, "0x7FFFFFFF"); // evals to 2147483647DateTime birthday = (DateTime) ExpressionEvaluator.GetValue(null, "date('1974/08/24')");DateTime exactBirthday = (DateTime) ExpressionEvaluator.GetValue(null, " date('19740824T131030', 'yyyyMMddTHHmmss')");bool trueValue = (bool) ExpressionEvaluator.GetValue(null, "true");object nullValue = ExpressionEvaluator.GetValue(null, "null");注意‘Tony\\'s Pizza’里多出來的那個反斜線是為了遵循C#的語法(按:在字符串中\\轉義為\)。數字中可以出現負號、指數冪和小數點。默認情況下,實數由Double.Parse來解析,除非指定了格式字符“M”或“F”。對于包含格式字符“M”的數字,將由Decimal.Parse解析;包含“F”的數字則由Single.Parse解析。如果在日期文本中指定了兩個參數(如上面例子倒數第三句),就會用DateTime.ParseExact進行解析。注意在解析時所有類型的Parse方法都會在內部引用CultureInfo.InvariantCulture作為當前的語言文化信息。
屬性,數組,列表,字典,索引器
前面表達式求值一節中曾經講到,用屬性路徑表示嵌套的屬性是很簡單的,只需要用小數點分隔嵌套的屬性名即可。例子中Invertor的兩個實例:pupin和tesla,已經用本章用到的示例類型中定義的數據賦過值了。如果要繼續“向下”訪問Tesla生日中的年份和Pupin出生地中的城市信息,可以用下面的代碼:
int year = (int) ExpressionEvaluator.GetValue(tesla, "DOB.Year")); // 1856string city = (string) ExpressionEvaluator.GetValue(pupin, "PlaCeOfBirTh.CiTy"); // "Idvor"眼尖的人可能已經發現了,pupin的出生地大小寫交替,顯得亂七八糟。這并非排版錯誤,而是有意為之,為的是證明表達式語言是忽略大小寫的。
在表達式中,可以用方括號獲取數組和列表的元素:
// Inventions Array string invention = (string) ExpressionEvaluator.GetValue(tesla, "Inventions[3]"); // "Induction motor"// Members List string name = (string) ExpressionEvaluator.GetValue(ieee, "Members[0].Name"); // "Nikola Tesla"// List and Array navigation string invention = (string) ExpressionEvaluator.GetValue(ieee, "Members[0].Inventions[6]") // "Wireless communication"在訪問字典的元素時,鍵值要用單引號括起來。在本例中,因為字典Officers的鍵值是字符串類型,我們可以直接使用文本字符串:
// Officer's Dictionary Inventor pupin = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers['president']";string city = (string) ExpressionEvaluator.GetValue(ieee, "Officers['president'].PlaceOfBirth.City"); // "Idvor"ExpressionEvaluator.SetValue(ieee, "Officers['advisors'][0].PlaceOfBirth.Country", "Croatia");也可以在方括號中直接使用其它表達式,例如變量名或某個類型的靜態屬性/方法名。我們會在其它小節中討論這些內容。
同樣的,索引器也用方括號訪問。下面是一個例子。也可以訪問多維索引器。
public class Bar {private int[] numbers = new int[] {1, 2, 3};public int this[int index]{get { return numbers[index];}set { numbers[index] = value; }} }Bar b = new Bar();int val = (int) ExpressionEvaluator.GetValue(bar, "[1]") // evaluated to 2ExpressionEvaluator.SetValue(bar, "[1]", 3); // set value to 3定義內聯的數組、列表和字典
除了能在表達式中引用容器中的數組、列表和詞典外,Spring.NET還允許在表達式中定義內聯數組、列表和詞典。內聯的列表用花括號定義,在花括號內,用逗號來分隔列表的元素。
{1, 2, 3, 4, 5} {'abc', 'xyz'}如果需要初始化一個強類型的數組,而非弱類型的列表,可以使用數組的初始化操作符:
new int[] {1, 2, 3, 4, 5} new string[] {'abc', 'xyz'}內聯字典的定義表達式有點復雜:首先需要在花括號前用#作為前綴,在花括號內,用逗號將鍵值對分隔開,鍵和值之間則用冒號隔開:
#{'key1' : 'Value 1', 'today' : DateTime.Today} #{1 : 'January', 2 : 'February', 3 : 'March', ...}用這種方式創建的數組、列表和字典可以在任意表達式中使用,稍后我們會在例子中看到。
請注意,雖然前面都是用簡單的文字值來定義數組/列表的項和詞典的鍵、值,但這只是一個簡單的例子——實際上我們可以用任何有效的表達式來定義元素。
方法
在文字表達式中,可以用標準的c#語法調用方法:
//string literal char[] chars = (char[]) ExpressionEvaluator.GetValue(null, "'test'.ToCharArray(1, 2)")) // 't','e'//date literal int year = (int) ExpressionEvaluator.GetValue(null, "date('1974/08/24').AddYears(31).Year") // 2005// object usage, calculate age of tesla navigating from the IEEE society.ExpressionEvaluator.GetValue(ieee, "Members[0].GetAge(date('2005-01-01')") // 149 (eww..a big anniversary is coming up ;)操作符
關系操作符
關系操作符:相等、不等、小于、小于等于、大于、大于等于在文字表達式里都用普通的符號表示。如果被比較的對象實現了IComparable接口,這些操作符就能起作用。另外,文字表達式也支持枚舉類型的關系操作,但如果要使用的枚舉不是mscorlib中的類型,則需要進行注冊,可參見類型注冊。
ExpressionEvaluator.GetValue(null, "2 == 2") // trueExpressionEvaluator.GetValue(null, "date('1974-08-24') != DateTime.Today") // trueExpressionEvaluator.GetValue(null, "2 < -5.0") // falseExpressionEvaluator.GetValue(null, "DateTime.Today <= date('1974-08-24')") // falseExpressionEvaluator.GetValue(null, "'Test' >= 'test'") // true下面是枚舉類型的比較:
FooColor fColor = new FooColor();ExpressionEvaluator.SetValue(fColor, "Color", KnownColor.Blue);bool trueValue = (bool) ExpressionEvaluator.GetValue(fColor, "Color == KnownColor.Blue"); //true其中FooColor的定義為:
public class FooColor {private KnownColor knownColor;public KnownColor Color{get { return knownColor;}set { knownColor = value; }} }除了標準的關系操作符,Spring.NET的表達式語言還支持一些功能很強大的特殊關系操作符,這些操作符是從SQL中“借”來的,比如in、like和between,以及is和matches等等,使用這些操作符可以判斷某個對象是否是特定的類型,或者判斷某個值是否和一個正則表達式相匹配。
ExpressionEvaluator.GetValue(null, "3 in {1, 2, 3, 4, 5}") // trueExpressionEvaluator.GetValue(null, "'Abc' like '[A-Z]b*'") // trueExpressionEvaluator.GetValue(null, "'Abc' like '?'") // falseExpressionEvaluator.GetValue(null, "1 between {1, 5}") // trueExpressionEvaluator.GetValue(null, "'efg' between {'abc', 'xyz'}") // trueExpressionEvaluator.GetValue(null, "'xyz' is int") // falseExpressionEvaluator.GetValue(null, "{1, 2, 3, 4, 5} is IList") // trueExpressionEvaluator.GetValue(null, "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'")) // falseExpressionEvaluator.GetValue(null, @"'5.00' matches '^-?\d+(\.\d{2})?$'") // true注意like操作符的匹配字符串使用的是VB的語法,而非SQL語法。(注意:在1.1的最終版中可能改變該行為,所以如果使用了like操作符,可能將來需要做必要的修改。)
邏輯操作符
邏輯操作符包括and,or和not,用法如下所示:
// AND bool falseValue = (bool) ExpressionEvaluator.GetValue(null, "true and false"); //falsestring expression = @"IsMember('Nikola Tesla') and IsMember('Mihajlo Pupin')"; bool trueValue = (bool) ExpressionEvaluator.GetValue(ieee, expression); //true// OR bool trueValue = (bool) ExpressionEvaluator.GetValue(null, "true or false"); //truestring expression = @"IsMember('Nikola Tesla') or IsMember('Albert Einstien')"; bool trueValue = (bool) ExpressionEvaluator.GetValue(ieee, expression); // true// NOT bool falseValue = (bool) ExpressionEvaluator.GetValue(null, "!true");// AND and NOT string expression = @"IsMember('Nikola Tesla') and !IsMember('Mihajlo Pupin')"; bool falseValue = (bool) ExpressionEvaluator.GetValue(ieee, expression);算術運算符
算術操作符可用于數字、字符串和日期的操作。其中數字和日期可以做減法。乘法和除法則只適用于數字。同時,也可以支持其它類型的數學操作符,如求模(%)和指數冪(^)。見下例:
// Addition int two = (int)ExpressionEvaluator.GetValue(null, "1 + 1"); // 2String testString = (String)ExpressionEvaluator.GetValue(null, "'test' + ' ' + 'string'"); //'test string'DateTime dt = (DateTime)ExpressionEvaluator.GetValue(null, "date('1974-08-24') + 5"); // 8/29/1974// Subtractionint four = (int) ExpressionEvaluator.GetValue(null, "1 - -3"); //4Decimal dec = (Decimal) ExpressionEvaluator.GetValue(null, "1000.00m - 1e4"); // 9000.00TimeSpan ts = (TimeSpan) ExpressionEvaluator.GetValue(null, "date('2004-08-14') - date('1974-08-24')"); //10948.00:00:00// Multiplicationint six = (int) ExpressionEvaluator.GetValue(null, "-2 * -3"); // 6int twentyFour = (int) ExpressionEvaluator.GetValue(null, "2.0 * 3e0 * 4"); // 24// Divisionint minusTwo = (int) ExpressionEvaluator.GetValue(null, "6 / -3"); // -2int one = (int) ExpressionEvaluator.GetValue(null, "8.0 / 4e0 / 2"); // 1// Modulusint three = (int) ExpressionEvaluator.GetValue(null, "7 % 4"); // 3int one = (int) ExpressionEvaluator.GetValue(null, "8.0 % 5e0 % 2"); // 1// Exponentint sixteen = (int) ExpressionEvaluator.GetValue(null, "-2 ^ 4"); // 16// Operator precedenceint minusFortyFive = (int) ExpressionEvaluator.GetValue(null, "1+2-3*8^2/2/2"); // -45賦值
在表達式中可以用等號為屬性賦值。因為ExpressionEvaluator類的SetValue方法可以直接用來賦值,所以用等號賦值一般是用在GetValue方法中。在用表達式列表組合多個操作數時,這種賦值方法就很有用,下一小節會討論表達式列表,現在先看一個使用賦值表達式的例子:
Inventor inventor = new Inventor(); String aleks = (String) ExpressionEvaluator.GetValue(inventor, "Name = 'Aleksandar Seovic'"); DateTime dt = (DateTime) ExpressionEvaluator.GetValue(inventor, "DOB = date('1974-08-24')");//Set the vice president of the society Inventor tesla = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers['vp'] = Members[0]");表達式列表
在花括號中用分號分隔多個表達式,可以使這些表達式同時(按:注意此處不是指并發,而是象編程語言中的語句塊一樣,“同時”執行)執行。表達式列表的返回值是列表中最后一個表達式的值。請看下面的例子:
//Perform property assignments and then return Name property.String pupin = (String) ExpressionEvaluator.GetValue(ieee.Members, "( [1].PlaceOfBirth.City = 'Beograd'; [1].PlaceOfBirth.Country = 'Serbia'; [1].Name )"));// pupin = "Mihajlo Pupin"類型
很多情況下,都可以通過名稱來引用一個類型:
ExpressionEvaluator.GetValue(null, "1 is int")ExpressionEvaluator.GetValue(null, "DateTime.Today")ExpressionEvaluator.GetValue(null, "new string[] {'abc', 'efg'}")如果類型是定義在mscorlib.dll中的,或是通過TypeRegistry注冊過的,都可以通過名稱直接引用。我們會在下一小節討論類型注冊。
對于其它類型,則需要使用特殊的T(typeName)表達式:
| 注意 |
| 類型的解析是由Spring.NET的ObjectUtils.ResolveType方法完成的,也就是說,定義在表達式中的類型,其解析方式和定義在配置文件中的類型是完全一樣的。 |
類型注冊
如果要在表達式中使用自定義類型(按:指沒有在mscorlib中定義的類型),需要先用TypeRegistry類進行注冊。注冊以后即可以用名稱來引用該類型,比如在new操作符(按:下一小節)或靜態屬性的引用表達式中就經常會用名稱來引用類型。請看下面的例子:
TypeRegistry.RegisterType("Society", typeof(Society));Inventor pupin = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers[Society.President]");此外,我們也可以在配置文件中用typeAliases節點注冊類型。
構造器
在表達式中,可以用new操作符調用類型的構造器。如果類型不是mscorlib中的標準類型,需要先進行注冊。請看下面的例子:
// simple ctor DateTime dt = (DateTime) ExpressionEvaluator.GetValue(null, "new DateTime(1974, 8, 24)");// Register Inventor type then create new inventor instance within Add method inside an expression list. // Then return the new count of the Members collection.TypeRegistry.RegisterType(typeof(Inventor)); int three = (int) ExpressionEvaluator.GetValue(ieee.Members, "{ Add(new Inventor('Aleksandar Seovic', date('1974-08-24'), 'Serbian')); Count}"));為方便起見,Spring.NET允許在表達式中定義已命名構造器參數,其作用是在對象被初始化之后對其屬性賦值,這與標準.NET特性的創建方式類似。比如,下面的表達式創建了一個Inventor實例,并對其Inventions屬性賦值。注意表達式中使用的Inventor構造器只有3個參數,最后一個等式就是已命名參數:
Inventor aleks = (Inventor) ExpressionEvaluator.GetValue(null, "new Inventor('Aleksandar Seovic', date('1974-08-24'), 'Serbian', Inventions = {'SPELL'})");這里唯一要遵守的規則是:已命名參數必須位于所有構造器參數之后,同.NET特性的創建方式一樣。
我們已經兩次提到.NET特性的創建方式,現在就來看看如何用Spring.NET的表達式語言為.NET特性創建實例。在創建.NET特性的實例時,我們可以用更為簡短和熟悉的方式來代替標準的構造器調用,如下:
WebMethodAttribute webMethod = (WebMethodAttribute) ExpressionEvaluator.GetValue(null, "@[WebMethod(true, CacheDuration = 60, Description = 'My Web Method')]");可以看出,除了其中的前綴@,創建特性所用的表達式與在C#中應用特性的語法在形式上完全一樣。
然爾語法上的輕微差異并不是特性表達式與構造器調用表達式之間的唯一區別。在使用特性表達式時,后臺所用的類型解析機制與調用構造器時也稍有不同,此時會同時嘗試按指定的類型名稱和按指定名稱加上"Attribute"后綴兩種方式來創建特性實例,這與C#編譯器的行為是一樣的。
變量
在表達式中用#variableName的格式來表示變量名。變量通過一個字典類型的參數傳遞給ExpressionEvaluator的GetValue或SetValue方法。(按:此處的變量是指表達式的變量,而非由c#定義在代碼中的變量;所有的變量都保存在一個字典中,變量名就是字典項的鍵值,變量值就是字典項的值。)
public static object GetValue(object root, string expression, IDictionary variables)public static void SetValue(object root, string expression, IDictionary variables, object newValue)變量名是字典的鍵值。請看下面的例子:
IDictionary vars = new Hashtable(); vars["newName"] = "Mike Tesla"; ExpressionEvaluator.GetValue(tesla, "Name = #newName", vars));表達式內部求值的結果也可以用字典來保存。下面的例子將Tesla的名字改回原來的值,并保存在鍵值為oldName的項中:
ExpressionEvaluator.GetValue(tesla, "{ #oldName = Name; Name = 'Nikola Tesla' }", vars); String oldName = (String)vars["oldName"]; // Mike Tesla在索引器中或map中,也可以使用變量名作為參數:?
vars["prez"] = "president"; Inventor pupin = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers[#prez]", vars);?'#this'和'#root'變量
在表達式中,有兩個特殊的變量:#this和#root。
#this變量可以顯式的引用表達式中當前節點的上下文:
// sets the name of the president and returns its instance ExpressionEvaluator.GetValue(ieee, "Officers['president'].( #this.Name = 'Nikola Tesla'; #this )")而#root變量用來引用表達式的根上下文:(按,注意它們的含義:表達式的“根上下文”,就是要在其上求值的對象,也就是說,是GetValue方法的第一個參數;而“表達式的節點”,是指表達式中以小數點分開的各個部分,比如node1.node2.node3,node3所在的上下文即為node2)
// removes president from the Officers dictionary and returns removed instance ExpressionEvaluator.GetValue(ieee, "Officers['president'].( #root.Officers.Remove('president'); #this )")三元操作符(If-Then-Else)
可以在表達式中用下面的三元操作符來表示if-then-else條件邏輯:
String aTrueString = (String) ExpressionEvaluator.GetValue(null, "false ? 'trueExp' : 'falseExp'") // trueExp上面的false值使得表達的返回值為“trueExp”。下面是一個更為真實的例子:
ExpressionEvaluator.SetValue(ieee, "Name", "IEEE"); IDictionary vars = new Hashtable(); vars["queryName"] = "Nikola Tesla";string expression = @"IsMember(#queryName) ? #queryName + ' is a member of the ' + Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";String queryResultString = (String) ExpressionEvaluator.GetValue(ieee, expression, vars));// queryResultString = "Nikola Tesla is a member of the IEEE Society"?列表的投影(Projection)和選擇(Selection)
列表的投影和選擇是表達式語言一項很強大的功能,可以從源列表中選出某些列或某些行來創建一個新列表。也就是說,投影可以看做是和SQL中Select語句功能相似的列選擇器,而選擇則相當于where子句。
例如,假設我們需要一個發明家的出生地列表,通過投影PlaceOfBirth.City屬性,很容易獲取此列表:
IList placesOfBirth = (IList) ExpressionEvaluator.GetValue(ieee, "Members.!{PlaceOfBirth.City}") // { 'Smiljan', 'Idvor' }我們也可以獲取社團中要員的名稱列表:
IList officersNames = (IList) ExpressionEvaluator.GetValue(ieee, "Officers.Values.!{Name}") // { 'Nikola Tesla', 'Mihajlo Pupin' }從這兩個例子可以看出,投影語句使用的語法是!{projectionExpression},它返回的新列表長度和原列表一樣,但元素的類型一般不相同。
另一方面,選擇語句的語法為?{selectionExpression},它返回的新列表是從源列表中過濾出的一個子集。例如,通過選擇語句我們很容易獲取一個塞爾維亞籍發明家的列表:
或者過濾出聲納的發明者:
IList sonarInventors = (IList) ExpressionEvaluator.GetValue(ieee, "Members.?{'Sonar' in Inventions}") // { pupin }或者,同時使用投影和選擇來獲取聲納發明者的全名:
IList sonarInventorsNames = (IList) ExpressionEvaluator.GetValue(ieee, "Members.?{'Sonar' in Inventions}.!{Name}") // { 'Mihajlo Pupin' }為方便起見,Spring.NET的表達式語言可以用專門的語法選擇第一個和最后一個匹配的列表項。注意這與正則表達式有區別,在用正則表達式選擇時,如果找不到匹配項就會返回一個空列表,而首尾項選擇表達式則會在找到匹配項時返回該項的引用,找不到時返回null。要返回第一個匹配項,要在選擇表達式中用^{代替?{,選擇最后一個匹配項時應該用${:
ExpressionEvaluator.GetValue(ieee, "Members.^{Nationality == 'Serbian'}.Name") // 'Nikola Tesla' ExpressionEvaluator.GetValue(ieee, "Members.${Nationality == 'Serbian'}.Name") // 'Mihajlo Pupin'請注意,在上面代碼中我們直接使用選擇結果訪問了Name屬性,因為通過首尾項選擇表達式返回的不是列表,而是列表中的元素。
集合處理器和聚合器(Aggregator)
除了列表的投影和選擇,Spring.NET表達式語言還支持幾種集合處理器,比如distinct、nonNull和sort;以及一系列通用的聚合器,如max、min、count、sum和average。
處理器和聚合器的區別是:處理器會返回一個新的或者轉換過的集合,而聚合器返回的則是一個單一值。除此之外它們非常相似——處理器和聚合器都使用標準的方法調用表達式來處理集合的節點,它們都很簡單,也很容易組成處理器鏈。
Count聚合器
通過Count聚合器,可以安全的獲取集合元素的總數。Count適用于所有集合類型,包括數組,我們不需要考慮應該使用Count屬性還是Length屬性來獲取集合的大小,并且,即便集合為null,也不會拋出NullReferenceException異常,而是返回0,在復雜的表達式中,這樣要比使用.NET的標準屬性更為安全。
ExpressionEvaluator.GetValue(null, "{1, 5, -3}.count()") // 3 ExpressionEvaluator.GetValue(null, "count()") // 0Sum聚合器
如果列表元素是數字值,可以用sum聚合器計算所有元素的總和。如果列表中數字的類型或精度不一致,則會自動執行必要的轉型,然后以其中最大精度的類型返回結果值。如果集合中的元素都不是數字,就會拋出InvalidArgumentException異常。
ExpressionEvaluator.GetValue(null, "{1, 5, -3, 10}.sum()") // 13 (int) ExpressionEvaluator.GetValue(null, "{5, 5.8, 12.2, 1}.sum()") // 24.0 (double)Average聚合器
average聚合器用于返回數字集合所有元素的平均值。在類型上,average使用和sum一樣的規則以最大程度的保證求值的精度。如果集合中的元素都不是數字,也和sum一樣,拋出InvalidArgumentException。
ExpressionEvaluator.GetValue(null, "{1, 5, -4, 10}.average()") // 3 ExpressionEvaluator.GetValue(null, "{1, 5, -2, 10}.average()") // 3.5Minimum聚合器
minimun聚合器返回列表元素中的最小值。為了確定“最小值”的真正含義,minimun要求目標集合中所有元素的類型相同并且都實現了IComparable接口。否則,該聚合器會拋出InvalidArgumentException異常。
ExpressionEvaluator.GetValue(null, "{1, 5, -3, 10}.min()") // -3 ExpressionEvaluator.GetValue(null, "{'abc', 'efg', 'xyz'}.min()") // 'abc'?Maximum聚合器
maximum聚合器返回列表元素中的最大值。為了確定“最大值”的真正意義,maximum要求目標集合中所有元素的類型相同并且實現了IComparable接口。否則,該聚合器會拋出InvalidArgumentException異常。
ExpressionEvaluator.GetValue(null, "{1, 5, -3, 10}.max()") // 10 ExpressionEvaluator.GetValue(null, "{'abc', 'efg', 'xyz'}.max()") // 'xyz'?nonNull處理器
nonNull處理器非常簡單,作用是從集合中清除所有空值(null)。
ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', null, 'abc', 'def', null}.nonNull()") // { 'abc', 'xyz', 'abc', 'def' } ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', null, 'abc', 'def', null}.nonNull().distinct().sort()") // { 'abc', 'def', 'xyz' }?distinct處理器
distinct處理器可以確保集合中不包含重復的元素。該處理器還可以接收一個可選的布爾參數,用來決定結果中是否應該包含空值。該參數默認值是false,也就是說結果集合不包含空值。
ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', 'abc', 'def', null, 'def' }.distinct(true).sort()") // { null, 'abc', 'def', 'xyz' } ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', 'abc', 'def', null, 'def' }.distinct(false).sort()") // { 'abc', 'def', 'xyz' }sort處理器
如果集合元素的類型統一且實現了IComparable接口,就可以用sort處理器進行排序。
ExpressionEvaluator.GetValue(null, "{1.2, 5.5, -3.3}.sort()") // { -3.3, 1.2, 5.5 } ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', 'abc', 'def', null, 'def' }.sort()") // { null, 'abc', 'abc', 'def', 'def', 'xyz' }引用容器中的對象
表達式也可以引用定義在IoC容器中的對象,語法為@contextName:objectName(按:contextName是指XML中<context>節點的名字,而非代碼中的IApplicaitonContext變量的名稱)。如果找不到以contextName命名的上下文,就使用根上下文的名字(Spring.RootContext)。可以參考IoC快速入門中的例子,下面的代碼可以返回由Roberto Benigni指導的電影。(按:Roberto Benigni,羅伯特·貝尼尼,意大利著名演員,代表作有《美麗人生》、《皮諾曹》等,有不少是他自己編劇或導演的)
public static void Main() {. . .// Retrieve context defined in the spring/context section of // the standard .NET configuration file. IApplicationContext ctx = ContextRegistry.GetContext();int numMovies = (int) ExpressionEvaluator.GetValue(null, "@(MyMovieLister).MoviesDirectedBy('Roberto Benigni').Length");. . . }最后,變量numMovies的值為2。
表達式
Lambda表達式是Spring.NET表達式語言中一項稍顯復雜但非常強大的功能。Lambda表達式允許在表達式中定義內聯函數,并且可以象真正的函數或方法一樣在表達式中調用。
定義Lambda表達式的語法如下:
#functionName = {|argList| functionBody }
例如,可以在表達式中定義一個max函數并調用它:
ExpressionEvaluator.GetValue(null, "(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", new Hashtable()) // 25可以看到,在函數體內引用前面定義的參數時,要使用本地變量的語法,即$加變量名。在調用時,將參數值以逗號分隔,列在函數名后的小括號內。
Lambda表達式支持遞歸,也就是說可以在函數體內部調用自身:
ExpressionEvaluator.GetValue(null, "(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))", new Hashtable()) // 120請注意,在上面的兩個例子中,我們必須為GetValue方法指定一個字典類型的參數,這里我們用哈希表。因為Lambda表達式實際上只是一些參數化變量,我們需要用一個字典來保存這些變量。如果不給GetValue方法指定一個有效的IDictionary實例,就會發生運行時錯誤。請參考10.3.10.節的相關內容。
另外,上面兩個例子都是在同一表達式內定義和調用函數的。但一般來說我們希望定義了函數后能夠在多個表達式中使用,Spring.NET通過一個靜態方法Expression.RegisterFunction來預注冊Lambda表達式,該方法接受函數名,Lambda表達式和存放變量的字典作為參數,如下:
IDictionary vars = new Hashtable(); Expression.RegisterFunction("sqrt", "{|n| Math.Sqrt($n)}", vars); Expression.RegisterFunction("fact", "{|n| $n <= 1 ? 1 : $n * #fact($n-1)}", vars);一旦注冊了函數,就可以在其它表達式中使用它們,注意要確保在每次求值時將字典變量vars傳給了表達式求值引擎:
ExpressionEvaluator.GetValue(null, "#fact(5)", vars) // 120 ExpressionEvaluator.GetValue(null, "#sqrt(9)", vars) // 3最后,請注意Lambda表達式本身也是按變量來處理的,它們可以被賦值給其它變量或作為參數傳遞給其他Lamba表達式。在下面的例子中,我們定義了一個委托函數,它有兩個參數:第一個f要調用的函數,第二個n則是要傳遞給f的值。隨后我們通過這個委托來調用前面注冊過的兩個函數,然后又調用了一個內聯函數:
Expression.RegisterFunction("delegate", "{|f, n| $f($n) }", vars); ExpressionEvaluator.GetValue(null, "#delegate(#sqrt, 4)", vars) // 2 ExpressionEvaluator.GetValue(null, "#delegate(#fact, 5)", vars) // 120 ExpressionEvaluator.GetValue(null, "#delegate({|n| $n ^ 2 }, 5)", vars) // 25雖然這個例子不算實用,但它確實證明了一點:Lambda表達式其實是參數化的變量,我們要牢記這一點。
空目標(Null Context)
如果沒有在GetValue方法中指定根對象而使用了null(按:即第一個參數,前面已經有多處這樣的用法)。那么表達式必須滿足下面的某種情況:
-
字面值,如:ExpressionEvaluator.GetValue(null, "2 + 3.14");
-
引用某個類型的靜態方法或字段,如:ExpressionEvaluator.GetValue(null,"DateTime.Today");
-
創建新的對象,如:ExpressionEvaluator.GetValue(null,"new DateTime(2004, 8, 14)");
-
或是引用其它對象,比如定義在變量詞典或IoC容器中的對象。
后兩種用法以后會專門討論。
本章使用的示例類型
下面的類是在本章例子中使用的類型:
public class Inventor {public string Name;public string Nationality;public string[] Inventions;private DateTime dob;private Place pob;public Inventor() : this(null, DateTime.MinValue, null){}public Inventor(string name, DateTime dateOfBirth, string nationality){this.Name = name;this.dob = dateOfBirth;this.Nationality = nationality;this.pob = new Place();}public DateTime DOB{get { return dob; }set { dob = value; }}public Place PlaceOfBirth{get { return pob; }}public int GetAge(DateTime on){// not very accurate, but it will do the job ;-)return on.Year - dob.Year;} }public class Place {public string City;public string Country; }public class Society {public string Name;public static string Advisors = "advisors";public static string President = "president";private IList members = new ArrayList();private IDictionary officers = new Hashtable();public IList Members{get { return members; }}public IDictionary Officers{get { return officers; }}public bool IsMember(string name){bool found = false;foreach (Inventor inventor in members){if (inventor.Name == name){found = true;break;}}return found;} }下面是例子中使用的數據:
Inventor tesla = new Inventor("Nikola Tesla", new DateTime(1856, 7, 9), "Serbian"); tesla.Inventions = new string[]{"Telephone repeater", "Rotating magnetic field principle","Polyphase alternating-current system", "Induction motor","Alternating-current power transmission", "Tesla coil transformer","Wireless communication", "Radio", "Fluorescent lights"}; tesla.PlaceOfBirth.City = "Smiljan";Inventor pupin = new Inventor("Mihajlo Pupin", new DateTime(1854, 10, 9), "Serbian"); pupin.Inventions = new string[] {"Long distance telephony & telegraphy", "Secondary X-Ray radiation", "Sonar"}; pupin.PlaceOfBirth.City = "Idvor"; pupin.PlaceOfBirth.Country = "Serbia";Society ieee = new Society(); ieee.Members.Add(tesla); ieee.Members.Add(pupin); ieee.Officers["president"] = pupin; ieee.Officers["advisors"] = new Inventor[] {tesla, pupin};?
轉載于:https://www.cnblogs.com/myssh/archive/2009/06/11/1501701.html
總結
以上是生活随笔為你收集整理的表达式求值Spring.Expressions的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux100day(day7)--用
- 下一篇: Spring Boot文件上传