《C#本质论》读书笔记(12)委托和Lambda表达式


12.1.委托概述

12.1.2 委托的数据类型

为了减少重复代码数量,可以将比较方法作为参数传递给 BubbleSort()方法。此外,为了将方法作为参数传递,必须有一个能够标识方法的数据类型——也就是委托。这里的委托类型是 ComparisonHandler 。
 c# 2.0之前的写法
  1. class DelegateSample  

  2.     {  

  3.         static void Main(string[] args)  

  4.         {  

  5.             //int[] arr = { 10, 20, 30, 40, 50 };  

  6.             int[] arr = { 50, 40, 30, 20, 10 };  

  7.   

  8.             ConsoleArr(arr);  

  9.   

  10.             ComparisonHandler wx = new ComparisonHandler(DelegateSample.IsTrue);  

  11.             BubbleSort(arr, wx);  

  12.       //C#2.0之前是这么写的


  13.       //BubbleSort(arr, new ComparisonHandler(IsTrue));  
  14.   

  15.             ConsoleArr(arr);  

  16.   

  17.             Console.Read();  

  18.         }  

  19.   

  20.         public delegate bool ComparisonHandler(int a, int b);  

  21.   

  22.         public static bool IsTrue(int a, int b)  

  23.         {  

  24.                 return a > b;  

  25.         }  

  26.   

  27.         public static void BubbleSort(int[] items, ComparisonHandler comparisonMethod)  

  28.         {  

  29.             int i;  

  30.             int j;  

  31.             int temp;  

  32.             if (items == null)  

  33.             {  

  34.                 return;  

  35.             }  

  36.             if (comparisonMethod == null)  

  37.             {  

  38.                 throw new ArgumentNullException("comparisonMethod");  

  39.             }  

  40.             for (i = items.Length - 1; i >= 0; i--)  

  41.             {  

  42.                 for (j = 1; j <= i; j++)  

  43.                 {  

  44.                     if (comparisonMethod(items[j - 1], items[j]))  

  45.                     {  

  46.                         temp = items[j - 1];  

  47.                         items[j - 1] = items[j];  

  48.                         items[j] = temp;  

  49.                     }  

  50.                 }  

  51.             }  

  52.         }  

  53.   

  54.         public static void ConsoleArr(int[] arr)  

  55.         {  

  56.             foreach (var item in arr)  

  57.             {  

  58.                 Console.Write(item+",");  

  59.             }  

  60.             Console.WriteLine();  

  61.         }  

  62.     }  



C#2.0以后可以直接调用方法
 
  1. public static bool AlphabeticalIsTrue(int a,int b)  

  2.         {  

  3.             int comparison;  

  4.             comparison = (a.ToString().CompareTo(b.ToString()));  

  5.             return comparison > 0;  

  6.         }  

  7.   

  8.   

  9. //C# 2.0以后直接传递方法  

  10.             BubbleSort(arr, AlphabeticalIsTrue); 


12.1.3 委托内部机制

第一个属性属于 System.Reflection.MethodiInfo 类型,MethodInfo 定义一个特定方法的签名,其中包括方法的名称、参数和返回类型。除了 MethodInfo,委托还需要一个对象实例,其中包含了要调用的方法。这正式第二个属性 Target 的用途。在静态方法的情况下,Target 对应于类型自身。

  1. // 摘要:   

  2. //     初始化一个委托,该委托对指定的类实例调用指定的实例方法。  

  3. //  

  4. // 参数:   

  5. //   target:  

  6. //     类实例,委托对其调用 method。  

  7. //  

  8. //   method:  

  9. //     委托表示的实例方法的名称。  

  10. //  

  11. // 异常:   

  12. //   System.ArgumentNullException:  

  13. //     target 为 null。 - 或 - method 为 null。  

  14. //  

  15. //   System.ArgumentException:  

  16. //     绑定到目标方法时出错。  

  17. [SecuritySafeCritical]  

  18. protected Delegate(object target, string method);  

  19. //  

  20. // 摘要:   

  21. //     初始化一个委托,该委托从指定的类调用指定的静态方法  

  22. //  

  23. // 参数:   

  24. //   target:  

  25. //     System.Type,它表示定义 method 的类。  

  26. //  

  27. //   method:  

  28. //     委托表示的静态方法的名称。  

  29. //  

  30. // 异常:   

  31. //   System.ArgumentNullException:  

  32. //     target 为 null。 - 或 - method 为 null。  

  33. //  

  34. //   System.ArgumentException:  

  35. //     target 不是 RuntimeType。 请参见 反射中的运行时类型。 - 或 - target 表示开放式泛型类型。  

  36. [SecuritySafeCritical]  

  37. protected Delegate(Type target, string method);  



12.2.匿名方法


传递一个匿名方法

  1. class Program 

  2. {  

  3.     public delegate bool ComparisonHandler(int a, int b);  

  4.     static void Main(string[] args)  

  5.     {  

  6.         int i;  

  7.         int[] items = new int[5];  

  8.         ComparisonHandler comparionMethod;  

  9.         for (i = 0; i < items.Length; i++)  

  10.         {  

  11.             Console.WriteLine("Enter an integer:");  

  12.             items[i] = int.Parse(Console.ReadLine());  

  13.         }  

  14.   

  15.        comparionMethod = delegate(int first, int second)   

  16.         {                                                                   

  17.             return first < second;                                    

  18.         };                                                                  

  19.         BubbleSort(items, comparionMethod);                   

  20.   

  21.         for ( i = 0; i < items.Length; i++)  

  22.         {  

  23.             Console.WriteLine(items[i]);  

  24.         }  

  25.   

  26.         Console.Read();  

  27.           

  28.     }  

  29.   

  30.     public static void BubbleSort(int[] items, ComparisonHandler comparisonMethod)  

  31.     {  

  32.         int i;  

  33.         int j;  

  34.         int temp;  

  35.         if (items == null)  

  36.         {  

  37.             return;  

  38.         }  

  39.         if (comparisonMethod == null)  

  40.         {  

  41.             throw new ArgumentNullException("comparisonMethod");  

  42.         }  

  43.         for (i = items.Length - 1; i >= 0; i--)  

  44.         {  

  45.             for (j = 1; j <= i; j++)  

  46.             {  

  47.                 if (comparisonMethod(items[j - 1], items[j]))  

  48.                 {  

  49.                     temp = items[j - 1];  

  50.                     items[j - 1] = items[j];  

  51.                     items[j] = temp;  

  52.                 }  

  53.             }  

  54.         }  

  55.     }  

  56. }  



12.3.系统定义的委托:Func 和 Action 声明 

在.NET3.5(C# 3.0)中,存在一系列名为“Action”和“Func”的泛型委托。
System.Func 代表有返回类型的委托,而 System.Action 代表无返回类型的委托。
 
.NET 委托类型不具备结构的相等性(structural equality)。不能将某个委托类型对象引用转换为不相关的委托类型,即使这两个委托类型的形参和返回类型完全一致。例如,这里就不能将 ComparisonHandler 引用直接赋给一个Func<int,int,bool>变量。
遗憾的是,需要结构一致但不相关的委托类型的情况下,为了使用给定的委托,唯一的办法是创建一个新委托。让它引用旧委托的 Invoke 方法。假定有一个 ComparisonHandler 变量 c,需要把 c 赋值给 Func<int,int,bool>类型的变量 f ,那么可以写成 f = c.Invoke;
Invoke说明

公共语言运行时提供 Invoke 每种委托类型,具有相同的签名与委托的方法。 您不需要显式调用此方法,从 C#、 Visual Basic 或 Visual c + +,因为编译器会自动调用。 Invoke 方法就很有用 反射 如果想要查找的委托类型签名。

https://msdn.microsoft.com/zh-cn/library/system.delegate.aspx



12.4.语句Lambda 

1.无参数的语句

即使无参数的语句lambda(代表无输入参数的委托),也要输入一对空白的圆括号
  1. static void Main(string[] args)  

  2. {  

  3.     //...  

  4.     Func<string> getUserInput =  

  5.         () =>  

  6.         {  

  7.             string input;  

  8.             do  

  9.             {  

  10.                 input = Console.ReadLine();  

  11.             }  

  12.             while (input.Trim().Length == 0);  

  13.             return input;  

  14.         };  

  15.     //...  

  16. }  

圆括号规则的一个例外是,当编译器能推断出数据类型,而且只有一个参数的时候。语句Lambda可以不带圆括号。

2.只有一个参数的语句

  1. IEnumerable<Process> processes = Process.GetProcesses()  

  2. .Where(process => { return process.WorkingSet64 > 100000000; });  

Where() 返回的是对物理内存占用超过 1GB 的进程的一个查询

12.5.表达式Lambda 

语句Lambda含有一个语句块,所以可以包含零个或者更多的语句,
表达式Lambda比语句Lambda更进一步。语句Lambda的代码块都只由一个return语句构成。其实在这种lambda块中,唯一需要就是准备返回的表达式。其他可以省略。
使用一个表达式Lambda来传递委托
  1. BubbleSort(items, (first, second) => first > second);  

语句lambda的比较


  1. BubbleSort(items, (first, second) => 

  2.     {

  3.         return first > second;

  4.     }

  5. );  



12.6.表达式树 


1.Lambda表达式作为数据使用

来看看下面的表达式
    class Program
    {
        static void Main(string[] args)
        {
            Person[] persons = new Person[3];
            IEnumerable<Person> obj = persons.Where(person => person.Name.ToUpper() == "INIGO MONTOYA");
        }
    }

    public class Person
    {
        public string Name { get; set; }
    } 
Lambda对应 Where 的是一具有委托类型的 Func<Person,bool>.
现在假定persons不是 Person[ ] 类型,而是代表远程数据库的对象,表中含有数百万人的数据。表中每一行信息都可以从服务器传输到客户端,客户端可以创建一个Person对象代表那一行。在客户端调用Where执行查询,如何判断结果?
  1. 一个技术是将几百万数据传输到客户端,为每一行创建一个Person对象,根据Lambda创建一个委托,再针对每个Person执行这个委托。概念上和数组的情况一致,但代价过于昂贵。

  2. .第二个技术要好很多,将Lambda的含义(过滤掉姓名不是INIGO MONTOYA的每一行)发给服务器。服务器将符合条件的少数几行传输到客户端;而不是先创建几百万个Person对象,再丢弃。但是没在那将Lambda的含义发送给服务器?


这正式语言中添加表达式树这一概念的动机。
 传给Where()的表达式树指出Lambda实参由以下几个几部分组成:
  1. 对Person的Name属性的调用;

  2. 对string的ToUpper()方法调用;

  3. 一个常量值“INIGO MONTOYA”;

  4. 一个相等性操作符==。

Where()方法获取这些数据并转换成一个 SQL where 子句。

2.表达式树作为对象图使用

表达式树转换成的数据是一个对象图,由 System.Linq.Expressions.Expression 表示。图中的“”本身代表Lambda对象本身,这个对象引用代表参数、一个返回类型和一个主体表达式的对象。
 可以为一元表达式或二元表达式创建一个对象图
 UnaryExpression代表一个形如count++的表达式。它具有Expression类型单个子操作数(Operand)。BinaryExpression有两个子表达式,Left和Right。两个类型都通过NodeType属性标识具体的运算符。两者都是从基类Expression派生。其他还有约30中表达式类型,如NewExpression、ParameterExrepssion等。

3.委托和表达式树的比较

像Where() 这样的用于构建LINQ查询方法是扩展方法。
扩展了 IEnumerable<T> 接口方法获取委托参数,扩展 IQueryable<T> 接口的方法获取表达式树参数。

假定一个集合支持IEnumerable,可以像下面这样调用 Where():
IEnumerable<Person> obj = persons.Where(person => person.Name.ToUpper() == "INIGO MONTOYA"); 

在 System.Linq.Enumerable 类中声明的扩展方法签名:
  1. public static IEnumerable<TSource> Where<TSource>(  

  2. this IEnumerable<TSource> source,   

  3. Func<TSource, bool> predicate);   


在 System.Linq.Queryable 类声明的扩展方法签名:
  1. public static IQueryable<TSource> Where<TSource>(  

  2. this IQueryable<TSource> source,   

  3. Expression<Func<TSource, bool>> predicate)  


编译器根据persons在编译时的类型决定使用哪个扩展方法;如果是一个能转换成 IQueryable<Person>的类型,就选择来自  System.Linq.Queryable 的方法。它将Lambda转换成一个表达式树。
执行时,persons接收表达式树结构,构造一个SQL,在请求查询结果时传给数据库并生成结果。调用Where的结果是一个对象。
如果persons不能隐式转换成 IQueryable<Person>,但能隐式转换成 IEnumerable<Person>,那就选择来自System.Linq.Enumerable的方法,lambda被转换成一个委托,当请求查询时,将委托作为断言应用于集合的每个成员,并生成与断言匹配的结果,调用Where 的结果是一个对象。

4.解析表达式

将Lambda表达式转换成 System.Linq.Expressions.Expression<TDelegate> 将创建一个表达式树,而不是委托。前面说将
  1. (x,y)=>x>y  

这样的Lambda转换成
  1. Func<int,int,bool>  

这样的委托类型。如下面的代码,可以检查生成树对象,显示它结构相关的信息,还可以显示更复杂的表达式树信息。
要注意,将表达式树实例传给 Console.WriteLine(expression) 方法,会自动将表达式树转换成一个描述性字符串形式。为表达式树重写了 ToString() ,以便调试时看出表达式内容。

名称说明
公共属性受 Silverlight for Windows Phone 支持Body获取 lambda 表达式的主体。 (继承自 LambdaExpression。)
公共属性CanReduce指示可将节点简化为更简单的节点。 如果返回 true,则可以调用 Reduce() 以生成简化形式。 (继承自Expression。)
公共属性Name获取 lambda 表达式的名称。 (继承自 LambdaExpression。)
公共属性受 Silverlight for Windows Phone 支持NodeType返回此 Expression 的节点类型。 (继承自 LambdaExpression。)

在 Silverlight for Windows Phone Windows Phone OS 7.1 中,此成员是从 Expression.NodeType 中继承的。

在 XNA Framework Windows Phone OS 7.0 中,此成员是从 Expression.NodeType 中继承的。
公共属性受 Silverlight for Windows Phone 支持Parameters获取 lambda 表达式的参数。 (继承自 LambdaExpression。)
公共属性ReturnType获取 lambda 表达式的返回类型。 (继承自 LambdaExpression。)
公共属性TailCall获取一个值,该值指示是否将通过尾调用优化来编译 lambda 表达式。 (继承自 LambdaExpression。)
公共属性受 Silverlight for Windows Phone 支持Type获取此 Expression 所表示的表达式的静态类型。 (继承自 LambdaExpression。)

在 Silverlight for Windows Phone Windows Phone OS 7.1 中,此成员是从 Expression.Type 中继承的。

在 XNA Framework Windows Phone OS 7.0 中,此成员是从 Expression.Type 中继承的。
  1.    Expression<Func<intintbool>> expression;  

  2.     expression = (x, y) => x > y;  

  3.     Console.WriteLine("------{0}------",expression);  

  4.     PrintNode(expression.Body, 0);//expression.Body: (x > y)  [lambda 表达式的主体]  

  5.     Console.WriteLine();  

  6.     Console.WriteLine();  

  7.     expression = (x, y) => x * y > x + y;  

  8.     Console.WriteLine("------{0}------",expression);  

  9.     PrintNode(expression.Body, 0);                  //expression.Body: (x * y) > (x + y)  

  10.     /* 

  11.      * expression.Body: (x * y) > (x + y)  [lambda 表达式的主体] 

  12.      * BinaryExpression是否具有二进制运算符:true 

  13.      * expression.Left:(x * y) 

  14.      * BinaryExpression是否具有二进制运算符:true 

  15.      * expression.Left:x   expression.NodeType:Parameter 

  16.      * BinaryExpression是否具有二进制运算符:false 

  17.      * expression.Left:*   expression.NodeType:Multiply 

  18.      * ..... 

  19.      */  

  20.     Console.WriteLine();  

  21.     Console.WriteLine();  

  22.     Console.Read();  

  23. }  

  24.   

  25. public static void PrintNode(Expression expression, int indent)  

  26. {  

  27.     if (expression is BinaryExpression)   //具有二进制运算符的表达式  

  28.         PrintNode(expression as BinaryExpression, indent);  

  29.     else  

  30.         PrintSingle(expression, indent);  

  31. }  

  32.   

  33. private static void PrintNode(BinaryExpression expression, int indent)  

  34. {  

  35.     PrintNode(expression.Left, indent + 1);  

  36.     PrintSingle(expression, indent);  

  37.     PrintNode(expression.Right, indent + 1);  

  38. }  

  39.   

  40. private static void PrintSingle(Expression expression, int indent)  

  41. {  

  42.     Console.WriteLine("{0," + indent * 5 + "}{1}"""NodeToSting(expression));  

  43. }  

  44.   

  45. private static string NodeToSting(Expression expression)  

  46. {  

  47.     switch (expression.NodeType)  

  48.     {  

  49.         case ExpressionType.Multiply:  

  50.             return "*";  

  51.         case ExpressionType.Add:  

  52.             return "+";  

  53.         case ExpressionType.Divide:  

  54.             return "/";  

  55.         case ExpressionType.Subtract:  

  56.             return "-";  

  57.         case ExpressionType.GreaterThan:  

  58.             return ">";  

  59.         case ExpressionType.LessThan:  

  60.             return "<";  

  61.         default:  

  62.             return expression.ToString() + "(" + expression.NodeType.ToString() + ")";  

  63.     }  

  64. }  

证明表达式树是由零个或多个其他表达式树构成,代表Lambda的“根”通过Body属性引用Lambda的主体。每个表达式树节点都包含枚举类型ExpressionType的一个NodeType属性,描述它是哪一种表达式。

注意:Lambda语句不能转换成表达式树,只有表达式Lambda才能转为表达式树。









原文地址:https://www.cnblogs.com/tangge/p/6025037.html