引言
說明
由於博客園是個技術社區,所以我得顯得嚴謹點,這里留下幾點說明,我會在接下來的幾篇文章中(如果有的話)重復這個說明。
其一,這篇(或者系列,如果有的話)文章是為了和大家一起入門(注意不是指導)。所以所編寫的代碼僅僅是示例的,或者說是處於編寫中(完善中)的。
其二,至於為什么在學習的過程中就着手寫這些文章,那是因為我深深覺得作為入門,這些內容還是容易的,但是常常讓人卻而退步。比如在一周之前,我還問博客園中的另一位博主,請求資料。那個時候我還覺得非常困難,非常苦惱。但是,經過一些摸索,一些文章的指導之后,卻輕輕叩開了LINQ的門,一窺其瑰麗了。
其三,其實網上並不是沒有LINQ的教程(指編寫Provider)。但是“會”和不會往往隔了一點頓悟。就像“水門事件”一樣。所以作為初學者來和大家一起探討可以讓彼此更同步。
其四,這真的是一個非常有挑戰,非常有趣的內容。我接觸了之后就忍不住和大家一起分享,邀大家一起參與冒險。
最后,這里列出所有我參考的,覺得有價值的資源。
其一,MSDN的博客: http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx
這系列文章直接和本系列文章相關。07年的帖子,13年才發現,真該面壁思過。
其二,http://weblogs.asp.net/mehfuzh/archive/2007/10/04/writing-custom-linq-provider.aspx
待會會在文章中引用到這個博主寫的一個非常短小的Provider示例。
其三,博客園中某個博主的作品http://www.cnblogs.com/Terrylee/category/48778.html
大神的文章讀起來有點累,所以這系列我訪問了好幾次,愣是沒看懂怎么回事,不過里面有張圖挺不錯。
接上文
在上文中,我們從接口着手,為了構造自己的LINQ Provider而努力。我們把要實現兩個接口減少為只要實現一個接口。通過對剩下的一個接口中要實現的一組方法提供固定實現,現在我們僅需要實現一組方法就能完成工作了。但是,這正是最具挑戰的部分。我們將在這個方法中完成“轉換”,“查詢”,“轉化”三個步驟。其中,如何將LINQ查詢通過合適的邏輯轉換為一種目標查詢語句是最核心的部分。此外,為了進行優化,我們還需要處理緩存等一系列操作。是不是很復雜?我承認,如果一開始就考慮全部事項,對於我們這種初學者而言簡直就是災難。但是不要忘了,我們的目的是盡快的構造自己的Hello World,以讓自己充滿信心。就像編寫第一個App的時候,沒有考慮一開始就考慮using(或者include)是什么,System.Console(或者printf)是什么,為什么要用大括號,以及為什么要使用引號(同樣的,上篇文章中所采取的一些操作,都沒有進行仔細的梳理,姑且就像使用VS設計WinForm的時候生成的designer文件一樣,認同它)。Just do it,我們先搞定一種情形,然后再搞定第二種,第三種...
Just do it
目標
在SQL查詢中,使用的最頻繁的可能就是Where篩選了,LDAP查詢也一樣。所以我們就從Where開始。Where是擴展方法,從根本上講,它有兩個參數,第一個表示調用者(this),第二個接受一個謂詞,姑且認為是一個返回值為bool的委托好了。再簡單一點,我們認為這個委托的主題部分是string類型的Contains方法。嗯,其實就是“選取xx部分包含關鍵字xxx的用戶”這樣的篩選表達式。使用一個常見的表達式的話,就是形如:context.User.Where(u => u.Name.Contains("sample"))或者from u in context.User where u.Name.Contains("sample")【記為表達式A】的LINQ查詢。然后如果是直接使用LDAP查詢呢?Filter字符串就應該是(假定我們檢索UPN)(&(objectClass=User)(userPrincipalName=*sample*))【記為表達式B,雖然他只是字符串】。也就是說我們要把A轉換為B。
表達式目錄樹
http://www.cnblogs.com/Terrylee/archive/2008/08/01/custom-linq-provider-part-1-expression-tree.html
這篇文章初步說明了表達式目錄樹是啥。但是和我們現在要做的事情還不夠接近。所以我現在定義了一個User類型,然后在VS中寫了一個表達式來一窺究竟。
如圖,總體是一個lambda,但是我們只需要關心其body部分,users參數只是為了提供一個引用。Body部分是一個MethodCallExpression(NodeType部分被遮擋了),他包含兩個參數,第一個參數是數據源,第二個參數是謂詞表達式。觀察第二個參數的時候,我們或許有點驚訝,因為不管怎么看,它都是個lambda表達式,而這里的NodeType卻是Quote。需要注意的就是:作為參數的lambda表達式會被封裝為Quote,實際上是一個一元操作表達式,UnaryExpression。我們訪問時把他轉換為UnaryExpression,然后調用Operand屬性,就可以訪問到lambda表達式了。然后繼續分析,u=>u.Name.Contains("sample”)這個表達式是lambda,其body是u.Name.Contains("sample"),是一個MethodCallExpression。但是這里有個不同,Contains方法是一個實例方法,而Where方法是一個擴展方法(在靜態類型中定義的靜態方法),所以上一個表達式的Object(表示調用者)屬性為null,而這個並非是Null而是u.Name。u.Name是一個MemberExpression,NodeType為MemberAccess,表示成員訪問。重要的是可以從中訪問到CustomAttribute。接下來看表達式u.Name.Contains,訪問他的第一個參數可以獲取到關鍵字“sample”。
訪問表達式目錄樹
如果直接訪問表達式目錄樹,是不是顯得有點無從着手呢?在System.Linq.Expression名稱空間下已經存在了一個類型,ExpressionVisiter,提供了很多虛方法。其中提供方法Visit,該方法接受一個Expression作為參數,並通過判斷這個參數的類型而將參數傳遞給其他方法。簡單地說就是switch case然后“重定向”。我引用的MSDN中的文章給出了這個方法的部分實現(說部分是因為這個類型的實現由於時間推移可能已經發生的變更,但大致是這樣)。以下是源碼:(昨天太困了...代碼貼錯了...)

public abstract class ExpressionVisitor { protected ExpressionVisitor() { } protected virtual Expression Visit(Expression exp) { if (exp == null) return exp; switch (exp.NodeType) { case ExpressionType.Negate: case ExpressionType.NegateChecked: case ExpressionType.Not: case ExpressionType.Convert: case ExpressionType.ConvertChecked: case ExpressionType.ArrayLength: case ExpressionType.Quote: case ExpressionType.TypeAs: return this.VisitUnary((UnaryExpression)exp); case ExpressionType.Add: case ExpressionType.AddChecked: case ExpressionType.Subtract: case ExpressionType.SubtractChecked: case ExpressionType.Multiply: case ExpressionType.MultiplyChecked: case ExpressionType.Divide: case ExpressionType.Modulo: case ExpressionType.And: case ExpressionType.AndAlso: case ExpressionType.Or: case ExpressionType.OrElse: case ExpressionType.LessThan: case ExpressionType.LessThanOrEqual: case ExpressionType.GreaterThan: case ExpressionType.GreaterThanOrEqual: case ExpressionType.Equal: case ExpressionType.NotEqual: case ExpressionType.Coalesce: case ExpressionType.ArrayIndex: case ExpressionType.RightShift: case ExpressionType.LeftShift: case ExpressionType.ExclusiveOr: return this.VisitBinary((BinaryExpression)exp); case ExpressionType.TypeIs: return this.VisitTypeIs((TypeBinaryExpression)exp); case ExpressionType.Conditional: return this.VisitConditional((ConditionalExpression)exp); case ExpressionType.Constant: return this.VisitConstant((ConstantExpression)exp); case ExpressionType.Parameter: return this.VisitParameter((ParameterExpression)exp); case ExpressionType.MemberAccess: return this.VisitMemberAccess((MemberExpression)exp); case ExpressionType.Call: return this.VisitMethodCall((MethodCallExpression)exp); case ExpressionType.Lambda: return this.VisitLambda((LambdaExpression)exp); case ExpressionType.New: return this.VisitNew((NewExpression)exp); case ExpressionType.NewArrayInit: case ExpressionType.NewArrayBounds: return this.VisitNewArray((NewArrayExpression)exp); case ExpressionType.Invoke: return this.VisitInvocation((InvocationExpression)exp); case ExpressionType.MemberInit: return this.VisitMemberInit((MemberInitExpression)exp); case ExpressionType.ListInit: return this.VisitListInit((ListInitExpression)exp); default: throw new Exception(string.Format("Unhandled expression type: '{0}'", exp.NodeType)); } } protected virtual MemberBinding VisitBinding(MemberBinding binding) { switch (binding.BindingType) { case MemberBindingType.Assignment: return this.VisitMemberAssignment((MemberAssignment)binding); case MemberBindingType.MemberBinding: return this.VisitMemberMemberBinding((MemberMemberBinding)binding); case MemberBindingType.ListBinding: return this.VisitMemberListBinding((MemberListBinding)binding); default: throw new Exception(string.Format("Unhandled binding type '{0}'", binding.BindingType)); } } protected virtual ElementInit VisitElementInitializer(ElementInit initializer) { ReadOnlyCollection<Expression> arguments = this.VisitExpressionList(initializer.Arguments); if (arguments != initializer.Arguments) { return Expression.ElementInit(initializer.AddMethod, arguments); } return initializer; } protected virtual Expression VisitUnary(UnaryExpression u) { Expression operand = this.Visit(u.Operand); if (operand != u.Operand) { return Expression.MakeUnary(u.NodeType, operand, u.Type, u.Method); } return u; } protected virtual Expression VisitBinary(BinaryExpression b) { Expression left = this.Visit(b.Left); Expression right = this.Visit(b.Right); Expression conversion = this.Visit(b.Conversion); if (left != b.Left || right != b.Right || conversion != b.Conversion) { if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null) return Expression.Coalesce(left, right, conversion as LambdaExpression); else return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method); } return b; } protected virtual Expression VisitTypeIs(TypeBinaryExpression b) { Expression expr = this.Visit(b.Expression); if (expr != b.Expression) { return Expression.TypeIs(expr, b.TypeOperand); } return b; } protected virtual Expression VisitConstant(ConstantExpression c) { return c; } protected virtual Expression VisitConditional(ConditionalExpression c) { Expression test = this.Visit(c.Test); Expression ifTrue = this.Visit(c.IfTrue); Expression ifFalse = this.Visit(c.IfFalse); if (test != c.Test || ifTrue != c.IfTrue || ifFalse != c.IfFalse) { return Expression.Condition(test, ifTrue, ifFalse); } return c; } protected virtual Expression VisitParameter(ParameterExpression p) { return p; } protected virtual Expression VisitMemberAccess(MemberExpression m) { Expression exp = this.Visit(m.Expression); if (exp != m.Expression) { return Expression.MakeMemberAccess(exp, m.Member); } return m; } protected virtual Expression VisitMethodCall(MethodCallExpression m) { Expression obj = this.Visit(m.Object); IEnumerable<Expression> args = this.VisitExpressionList(m.Arguments); if (obj != m.Object || args != m.Arguments) { return Expression.Call(obj, m.Method, args); } return m; } protected virtual ReadOnlyCollection<Expression> VisitExpressionList(ReadOnlyCollection<Expression> original) { List<Expression> list = null; for (int i = 0, n = original.Count; i < n; i++) { Expression p = this.Visit(original[i]); if (list != null) { list.Add(p); } else if (p != original[i]) { list = new List<Expression>(n); for (int j = 0; j < i; j++) { list.Add(original[j]); } list.Add(p); } } if (list != null) { return list.AsReadOnly(); } return original; } protected virtual MemberAssignment VisitMemberAssignment(MemberAssignment assignment) { Expression e = this.Visit(assignment.Expression); if (e != assignment.Expression) { return Expression.Bind(assignment.Member, e); } return assignment; } protected virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding) { IEnumerable<MemberBinding> bindings = this.VisitBindingList(binding.Bindings); if (bindings != binding.Bindings) { return Expression.MemberBind(binding.Member, bindings); } return binding; } protected virtual MemberListBinding VisitMemberListBinding(MemberListBinding binding) { IEnumerable<ElementInit> initializers = this.VisitElementInitializerList(binding.Initializers); if (initializers != binding.Initializers) { return Expression.ListBind(binding.Member, initializers); } return binding; } protected virtual IEnumerable<MemberBinding> VisitBindingList(ReadOnlyCollection<MemberBinding> original) { List<MemberBinding> list = null; for (int i = 0, n = original.Count; i < n; i++) { MemberBinding b = this.VisitBinding(original[i]); if (list != null) { list.Add(b); } else if (b != original[i]) { list = new List<MemberBinding>(n); for (int j = 0; j < i; j++) { list.Add(original[j]); } list.Add(b); } } if (list != null) return list; return original; } protected virtual IEnumerable<ElementInit> VisitElementInitializerList(ReadOnlyCollection<ElementInit> original) { List<ElementInit> list = null; for (int i = 0, n = original.Count; i < n; i++) { ElementInit init = this.VisitElementInitializer(original[i]); if (list != null) { list.Add(init); } else if (init != original[i]) { list = new List<ElementInit>(n); for (int j = 0; j < i; j++) { list.Add(original[j]); } list.Add(init); } } if (list != null) return list; return original; } protected virtual Expression VisitLambda(LambdaExpression lambda) { Expression body = this.Visit(lambda.Body); if (body != lambda.Body) { return Expression.Lambda(lambda.Type, body, lambda.Parameters); } return lambda; } protected virtual NewExpression VisitNew(NewExpression nex) { IEnumerable<Expression> args = this.VisitExpressionList(nex.Arguments); if (args != nex.Arguments) { if (nex.Members != null) return Expression.New(nex.Constructor, args, nex.Members); else return Expression.New(nex.Constructor, args); } return nex; } protected virtual Expression VisitMemberInit(MemberInitExpression init) { NewExpression n = this.VisitNew(init.NewExpression); IEnumerable<MemberBinding> bindings = this.VisitBindingList(init.Bindings); if (n != init.NewExpression || bindings != init.Bindings) { return Expression.MemberInit(n, bindings); } return init; } protected virtual Expression VisitListInit(ListInitExpression init) { NewExpression n = this.VisitNew(init.NewExpression); IEnumerable<ElementInit> initializers = this.VisitElementInitializerList(init.Initializers); if (n != init.NewExpression || initializers != init.Initializers) { return Expression.ListInit(n, initializers); } return init; } protected virtual Expression VisitNewArray(NewArrayExpression na) { IEnumerable<Expression> exprs = this.VisitExpressionList(na.Expressions); if (exprs != na.Expressions) { if (na.NodeType == ExpressionType.NewArrayInit) { return Expression.NewArrayInit(na.Type.GetElementType(), exprs); } else { return Expression.NewArrayBounds(na.Type.GetElementType(), exprs); } } return na; } protected virtual Expression VisitInvocation(InvocationExpression iv) { IEnumerable<Expression> args = this.VisitExpressionList(iv.Arguments); Expression expr = this.Visit(iv.Expression); if (args != iv.Arguments || expr != iv.Expression) { return Expression.Invoke(expr, args); } return iv; } }
分析表達式是一件很有難度的事情,我必須承認我沒有從大學的《編譯原理》這門課中學到任何有意思的東西,並且現在很后悔。好在我們現在的目標不是特別遠,可以找一些規律然后就開始編碼。首先,對於(&(objectClass=User)(userPrincipalName=*sample*)),&表示同時滿足后面的兩個條件。外層括號是屬於整個表達式的。而只有第二個括號是來自Where的。那么我們就可以約定:在Where開始的時候附加左括號,在Where分析結束(包括其子表達式)之后附加右括號。由於設想的情形簡單,所以順序分析就可以了。由於是針對性非常強的轉換,實現起來就比較簡單,以下的我的實現。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Linq.Expressions; namespace Providers { public class DSTranslator:ExpressionVisitor { //用於拼接查詢字符串 private StringBuilder sb = new StringBuilder(); protected override Expression VisitMethodCall(MethodCallExpression node) { /*在LINQ查詢中,這往往是入口*/ if (node.Method.Name == "Where" && node.Method.DeclaringType == typeof(Queryable)) { /*保證是Queryable中定義的Where的方法*/ var arg1 = node.Arguments[0]; var arg2 = node.Arguments[1]; //順序分析 Visit(arg1); sb.Append("("); Visit(arg2); sb.Append(")"); } else if (node.Method.Name == "Contains" && node.Method.DeclaringType == typeof(string)) { var invoker = node.Object; Visit(invoker); //已經確定參數是字符串 string value = string.Format("=*{0}*", (node.Arguments[0] as ConstantExpression).Value); sb.Append(value); } else if (node.Method.Name == "StartsWith" && node.Method.DeclaringType == typeof(string)) { var invoker = node.Object; Visit(invoker); string value = string.Format("={0}*", (node.Arguments[0] as ConstantExpression).Value); sb.Append(value); } return node; } protected override Expression VisitConstant(ConstantExpression node) { var query = node.Value as IQueryable; if (query != null) { var type = query.ElementType; object categoryAttr = type.GetCustomAttributes( typeof(CategoryAttribute), false) .FirstOrDefault(); if (categoryAttr != null) { //如果是類型特性 sb.AppendFormat("(objectClass={0})", (categoryAttr as CategoryAttribute).Name); } } return node; } protected override Expression VisitUnary(UnaryExpression node) { /*訪問一元表達式*/ var opdNode = GetOperand(node); Visit(opdNode); return node; } public static Expression GetOperand(Expression node) { /*如果是Quot表達式就把獲取其操作數*/ while (node.NodeType == ExpressionType.Quote) { node = (node as UnaryExpression).Operand; } return node; } protected override Expression VisitLambda<T>(Expression<T> node) { /*這里通常是篩選的正文*/ var body = node.Body; Visit(body); return node; } protected override Expression VisitMember(MemberExpression node) { //獲取直接應用的Property特性 var propertyAttr = node.Member.GetCustomAttributes( typeof(PropertyAttribute), false) .FirstOrDefault(); if (propertyAttr != null) { sb.Append((propertyAttr as PropertyAttribute).Name); } return node; } public string Translate(Expression expression) { Visit(expression); return string.Format("(&{0})", sb.ToString()); } public void Clear() { sb.Clear(); } } }
我使用了Attribute來表示某個屬性代表LDAP中的什么屬性,以及某種類型代表LDAP中的哪種類型。並模擬了一個Context。代碼如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Providers { [Category("User")] public class User { /*模型類型,用來構造查詢*/ [Property("userPrincipalName")] public string UserPrincipalName { get; set; } [Property("cn")] public string Name { get; set; } } [Category("groupOfUniqueNames")] public class Group { } public class Context { public OLC.LINQ.Queryable<User> Users { get; private set; } public OLC.LINQ.Queryable<Group> Groups { get; private set; } public Context() { var provider = new DSProvider(); Groups = new OLC.LINQ.Queryable<Group>(provider); Users = new OLC.LINQ.Queryable<User>(provider); } } public class CategoryAttribute : Attribute { public CategoryAttribute(string categoryName) { Name = categoryName; } public string Name { get; private set; } } public class PropertyAttribute : Attribute { public PropertyAttribute(string propertyName) { Name = propertyName; } public string Name { get; private set; } } }
如你所見,這些代碼都是生造出來的,純粹為了我們的HelloWorld,沒有經過任何改進。最后看一看測試代碼以及結果。

using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Windows.Forms; namespace Providers { class Program { static void Main(string[] args) { Context context = new Context(); var lambda1 = context.Users.Where(u => u.Name.Contains("sample")); var lambda2 = context.Users.Where(u => u.UserPrincipalName.StartsWith("sample")); var lambda3 = context.Groups; DSTranslator translater = new DSTranslator(); string translatedStr = translater.Translate(lambda1.Expression); Console.WriteLine(translatedStr); translater.Clear(); translatedStr = translater.Translate(lambda2.Expression); Console.WriteLine(translatedStr); translater.Clear(); translatedStr = translater.Translate(lambda3.Expression); Console.WriteLine(translatedStr); } } }
稍微擴寬點
有結果出來就會覺得很開心,作為一個程序員我一直這么覺得。但是盡管是這么一個非常局限,非常確定的表達式轉換,我仍然花了很長時間來調試。正如@李永京博主說的,這是一條很長的路;有趣的路不是么。現在我們把問題放寬一點,如果謂詞表達式的主體是若干個邏輯運算的結果呢?還有就是我們現在的謂詞表達式通常和一個常量相關,比如StartsWith(“”),但如果是UserPrincipal.StartsWith(Name)呢...不知道LDAP服務端支不支持這樣的查詢,但是就算不支持,我們可以把數據加載了之后進行篩選,而使用的時候卻絲毫不需要考慮這個,這正是LINQ的美妙和美麗之處吧。
但暫時先不考慮這個,先考慮多個邏輯運算聯結的事情好了,畢竟很常見。用TreeView寫了個WinForm,結果發現多個邏輯表達式做“&&”和“||”的時候根節點的選取很奇怪,如果都是“&&”或者都是“||”則選取最后一個表達式作為右操作數,其余作為左操作數。但是“||”和“&&”混合的時候,規律就不明顯了。這里附上測試代碼和結果,讓大家幫忙分析下。還是第一次考慮“&&”和“||”的優先級以及結合性。但是網上的結果不大一致,有的說“||”和“&&”的優先級一致,也有的說“&&”的優先級高於“||”。需要時間把這個弄清楚。
以下是代碼。

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Windows.Forms; namespace Providers { public partial class ExpressionTree : Form { private ExpressionTree() { InitializeComponent(); } public ExpressionTree(Expression expression) : this() { //分析表達式目錄樹 analysis(expression); } public ExpressionTree(IEnumerable<Expression> expressions) : this() { foreach (var exp in expressions) analysis(exp); } public void analysis(Expression expression) { //深度遍歷 while (expression != null) { if (expression is LambdaExpression) { var lambdaExpression = expression as LambdaExpression; expression = lambdaExpression.Body; } else if (expression is MethodCallExpression) { //對於一個參數的擴展方法 expression = (expression as MethodCallExpression).Arguments.LastOrDefault(); } else if (expression.NodeType == ExpressionType.Quote) { expression = (expression as UnaryExpression).Operand; } else if (expression is BinaryExpression) { TreeNode root = new TreeNode(expression.ToString()); this.expressionViewer.Nodes.Add(root); analysisBinary(expression, root); break; } } } private void analysisBinary(Expression expression, TreeNode node) { //分析二元運算符 if (expression is BinaryExpression) { /*為了明了起見,這里附加上節點本身的運算符。*/ BinaryExpression be = expression as BinaryExpression; if (be.NodeType == ExpressionType.AndAlso || be.NodeType == ExpressionType.OrElse) { string nodeStr = node.Text; node.Text = string.Format("{0}[{1}]", nodeStr, be.NodeType); } TreeNode childNodeLeft = new TreeNode(be.Left.ToString()); node.Nodes.Add(childNodeLeft); analysisBinary(be.Left, childNodeLeft); TreeNode childNodeRight = new TreeNode(be.Right.ToString()); node.Nodes.Add(childNodeRight); analysisBinary(be.Right, childNodeRight); } //不處理非二元運算 } } }

static void showWindow() { Expression<Func<IEnumerable<User>, IEnumerable<User>>> expression1 = users => users.Where(u => (u.Name.Contains("test") || u.Name.Length < 10) && u.UserPrincipalName.StartsWith("H") || u.Name != "" && u.UserPrincipalName.Length > 10); Expression<Func<IEnumerable<User>, IEnumerable<User>>> expression2 = users => users.Where(u => (u.Name.Contains("test") || u.Name.Length < 10) || u.Name == "1" || u.Name == "2" || u.Name != "3" || u.Name != "4"); Expression<Func<IEnumerable<User>, IEnumerable<User>>> expression3 = users => users.Where(u => (u.Name.Contains("test") || u.Name.Length < 10) && (u.UserPrincipalName.StartsWith("H") || u.Name != "" && u.UserPrincipalName.Length > 10) && u.Name == "1"); ExpressionTree tree = new ExpressionTree(new Expression[] { expression1, expression2, expression3 }); Application.Run(tree); }
以下是結果。
[13/1/28補充邏輯運算(&&,||)優先級]

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; namespace Sample { class Program { static void Main(string[] args) { bool value1 = true && false || true && false || false; /*如果&&的優先級比||高或者相反,則右結合和左結合都無所謂*/ bool value2 = true && false || true && false || false && true || true; Console.WriteLine(value1); Console.WriteLine(value2); Console.ReadKey(); /*結果是 * false true*/ } } }
由上面的例子可以看出,&&的優先級高於||的優先級。后來又查閱了C#高級編程,P169。這里貼上一個表格來。如此一來,結合性就不那么重要了。
結語
看到這個標題就意味着這一篇也結束了。我個人性子比較急,想到什么就像寫出來,所以會導致內容質量很差,還希望大家別見怪。其實這篇本來可以作為完結篇的,因為接下來的事情無非是查詢,轉換。不過我又想到了一些東西,所以想在最后的時候作為補充。LINQ之路漫長,不過我會堅持下去的。:)