c# 表示式樹(一)

夢裡小探花發表於2020-11-07

前言

打算整理c# 程式碼簡化史系列,所以相關的整理一下,簡單的引出一下概念。

什麼是表示式樹呢?

表示式樹以樹形資料結構表示程式碼,其中每一個節點都是一種表示式,比如方法呼叫和 x < y 這樣的二元運算等。

這個是什麼意思呢?用結構表示程式碼? 用靜態的表示動態的,一般來說是某種約定。

比如計算機中的強弱電路,可能這樣不好理解。舉一個盒子的例子:

假設我要計算加法,那麼如果表示加法呢?我用一個盒型結構,把第一個數放在第一個位置,把第二個數放在第二個位置,然後第三個位置我傳入方法,表示第一個和第二個會執行第三個位置的方法,在這裡呢,還是結構,因為並沒有去執行,只是說組合了這樣一種結構。

現在呢,假設按照某種約定組合成一種結構,那麼這種就稱為表示式,就是用來表示某種情況的嘛。然後呢,現在這種表示式是樹,那麼就叫表示式樹了。

這裡介紹一下表示式,來增強一波:

然後再來透析一波:

正文

用一個例子來表示正則表達吧,例子是官網的,但是官網解釋的比較含糊,所以再來解釋一波吧。

官網用的一個例子是:Where(company => (company.ToLower() == "coho winery" || company.Length > 16)).orderby(company=>company)

那麼來看一下吧:

string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
	   "Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
	   "Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
	   "Blue Yonder Airlines", "Trey Research", "The Phone Company",
	   "Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };

// The IQueryable data to query.  
IQueryable<String> queryableData = companies.AsQueryable<string>();

有一個陣列,然後轉換成IQueryable 格式,這麼做的目的其實就是因為queryable 實現了一些expression的屬性。

好吧,暫時就不解釋這幾個引數的作用,後面看下去自然就明白了。

接著放程式碼:

ParameterExpression pe = Expression.Parameter(typeof(string), "company");

這個意思就是說建立了一個屬性是company的變數,相當於我們以前的xy,名字隨便取。

Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Expression right = Expression.Constant("coho winery");
Expression e1 = Expression.Equal(left, right);

因為其實樹結構,那麼這裡的引數left就是左子樹,right 就是右子樹。

left 呢,這個call 就是說pe(也就是company變數)將會執行一個方法,ToLower,對應的就是company.ToLower()。

然後右邊就是一個固定的引數coho winery,現在的表示式就是company.ToLower()=='coho winery',返回的是一個bool型別。

left = Expression.Property(pe, typeof(string).GetProperty("Length"));
right = Expression.Constant(16, typeof(int));
Expression e2 = Expression.GreaterThan(left, right);

接下來就是就是獲取compay的屬性Length,然後和int 型別相比,就是conpany.length>16
Expression predicateBody = Expression.OrElse(e1, e2);
那麼就是e1和e2相連,中間用的是or,company.ToLower() == "coho winery" || company.Length > 16 好的現在表示式完了,那麼如何和資料聯絡在一起呢?

// Create an expression tree that represents the expression  
// 'queryableData.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))'  
MethodCallExpression whereCallExpression = Expression.Call(
	typeof(Queryable),
	"Where",
	new Type[] { queryableData.ElementType },
	queryableData.Expression,
	Expression.Lambda<Func<string, bool>>(predicateBody, new ParameterExpression[] { pe }));
// ***** End Where *****  

// ***** OrderBy(company => company) *****  
// Create an expression tree that represents the expression  
// 'whereCallExpression.OrderBy(company => company)'  
MethodCallExpression orderByCallExpression = Expression.Call(
	typeof(Queryable),
	"OrderBy",
	new Type[] { queryableData.ElementType, queryableData.ElementType },
	whereCallExpression,
	Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
// ***** End OrderBy *****  

// Create an executable query from the expression tree.  
IQueryable<string> results = queryableData.Provider.CreateQuery<string>(orderByCallExpression);

whereCallExpression 和 orderByCallExpression 表示要執行的操作,用whereCallExpression 舉例目標型別是Queryable,呼叫where,然後表示式是predicateBody,引數是pe。

orderByCallExpression 類推。

最後一步就是傳遞表達:IQueryable results = queryableData.Provider.CreateQuery(orderByCallExpression);

裡面的實現是非常複雜的,我自己也沒有去看,因為覺得沒有必要,這種東西就是一個工具,誰要是這樣寫,那可正是思維邏輯不是一般的強,一般來說和彙編差不多。

需要明白的就是它不會立即去執行,而是就是一個表示式和其緊密連線。有興趣可以去了解iqueryable的實現,複雜的一批。

那麼不管其多麼複雜,就是本質上就是制定一套規則,我們按照它這個規則然後給我們填充,那麼就會對應相應的結果給我們,我們可能設計不出這麼好的表示式,但是有時候我們也會去製作相應的規則,比如說某種格式等,但你不要去想想它的程式碼多優雅,因為其穩定性很高,不需要追求優雅。

那麼我們為了延後實現,我們就要去這樣做嗎?如果這樣做的話,我想很多人會設計出另外一套,沒有這麼繁瑣,可能就是幾個引數,然後一個委託組合成一顆樹,雖然很大的侷限性,但是寫這樣的程式碼真的痛苦。

這個時候人們就想有沒有什麼能中間轉換一下的呢?比如說我寫一串字元,然後我就自動按照某種規則去解析不就可以了,但是這種有一個很不好的地方在於,字串是弱型別除錯相當麻煩,這時候就瞄準好了lambda了。

若 lambda 表示式被分配給 Expression<TDelegate> 型別的變數,則編譯器可以發射程式碼以建立表示該 lambda 表示式的表示式樹。

C# 編譯器只能從表示式 Lambda(或單行 Lambda)生成表示式樹。 它無法解析語句 lambda (或多行 lambda)。

舉個例子:

Expression<Func<int, bool>> lambda = num => num < 5;  

就可以使用lambda表示式進行一個expression的轉換。

從Expression到Expression 之間呢,還有一層,他們的繼承關係是

 Object
 Expression
 LambdaExpression
 Expression<TDelegate>

很多時候lambda 表示式轉換的表示式就可以為我們解決大部分問題,但是不要覺得這是Expression的全部,因為轉換的只有一行,然後expression還有很多是無法用lambda來表示的。

未完待續。

相關文章