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

时间:2023-03-09 09:27:31
《C#本质论》读书笔记(12)委托和Lambda表达式
《C#本质论》读书笔记(12)委托和Lambda表达式

12.1.委托概述

12.1.2 委托的数据类型

为了减少重复代码数量,可以将比较方法作为参数传递给 BubbleSort()方法。此外,为了将方法作为参数传递,必须有一个能够标识方法的数据类型——也就是委托。这里的委托类型是 ComparisonHandler 。
《C#本质论》读书笔记(12)委托和Lambda表达式
 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. ConsoleArr(arr);

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

  9. BubbleSort(arr, wx);

  10.       //C#2.0之前是这么写的
  11.       //BubbleSort(arr, new ComparisonHandler(IsTrue));  
  12. ConsoleArr(arr);

  13. Console.Read();

  14. }

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

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

  17. {

  18. return a > b;

  19. }

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

  21. {

  22. int i;

  23. int j;

  24. int temp;

  25. if (items == null)

  26. {

  27. return;

  28. }

  29. if (comparisonMethod == null)

  30. {

  31. throw new ArgumentNullException("comparisonMethod");

  32. }

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

  34. {

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

  36. {

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

  38. {

  39. temp = items[j - 1];

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

  41. items[j] = temp;

  42. }

  43. }

  44. }

  45. }

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

  47. {

  48. foreach (var item in arr)

  49. {

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

  51. }

  52. Console.WriteLine();

  53. }

  54. }



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. //C# 2.0以后直接传递方法

  8. 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);

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


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. comparionMethod = delegate(int first, int second)

  15. {

  16. return first < second;

  17. };

  18. BubbleSort(items, comparionMethod);

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

  20. {

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

  22. }

  23. Console.Read();

  24. }

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

  26. {

  27. int i;

  28. int j;

  29. int temp;

  30. if (items == null)

  31. {

  32. return;

  33. }

  34. if (comparisonMethod == null)

  35. {

  36. throw new ArgumentNullException("comparisonMethod");

  37. }

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

  39. {

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

  41. {

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

  43. {

  44. temp = items[j - 1];

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

  46. items[j] = temp;

  47. }

  48. }

  49. }

  50. }

  51. }


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

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

在.NET3.5(C# 3.0)中,存在一系列名为“Action”和“Func”的泛型委托。
System.Func 代表有返回类型的委托,而 System.Action 代表无返回类型的委托。
《C#本质论》读书笔记(12)委托和Lambda表达式
 《C#本质论》读书笔记(12)委托和Lambda表达式
.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的含义发送给服务器?

这正式语言中添加表达式树这一概念的动机。

《C#本质论》读书笔记(12)委托和Lambda表达式
 传给Where()的表达式树指出Lambda实参由以下几个几部分组成:
  1. 对Person的Name属性的调用;

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

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

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

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

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

表达式树转换成的数据是一个对象图,由 System.Linq.Expressions.Expression 表示。图中的“根”本身代表Lambda对象本身,这个对象引用代表参数、一个返回类型和一个主体表达式的对象。
《C#本质论》读书笔记(12)委托和Lambda表达式
 可以为一元表达式或二元表达式创建一个对象图
《C#本质论》读书笔记(12)委托和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() ,以便调试时看出表达式内容。

名称 说明
《C#本质论》读书笔记(12)委托和Lambda表达式《C#本质论》读书笔记(12)委托和Lambda表达式 Body 获取 lambda 表达式的主体。 (继承自 LambdaExpression。)
《C#本质论》读书笔记(12)委托和Lambda表达式 CanReduce 指示可将节点简化为更简单的节点。 如果返回 true,则可以调用 Reduce() 以生成简化形式。 (继承自Expression。)
《C#本质论》读书笔记(12)委托和Lambda表达式 Name 获取 lambda 表达式的名称。 (继承自 LambdaExpression。)
《C#本质论》读书笔记(12)委托和Lambda表达式《C#本质论》读书笔记(12)委托和Lambda表达式 NodeType Expression." xml:space="preserve">返回此 Expression 的节点类型。 (继承自 LambdaExpression。)

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

在 XNA Framework Windows Phone OS 7.0 中,此成员是从 Expression.NodeType 中继承的。
《C#本质论》读书笔记(12)委托和Lambda表达式《C#本质论》读书笔记(12)委托和Lambda表达式 Parameters 获取 lambda 表达式的参数。 (继承自 LambdaExpression。)
《C#本质论》读书笔记(12)委托和Lambda表达式 ReturnType 获取 lambda 表达式的返回类型。 (继承自 LambdaExpression。)
《C#本质论》读书笔记(12)委托和Lambda表达式 TailCall 获取一个值,该值指示是否将通过尾调用优化来编译 lambda 表达式。 (继承自 LambdaExpression。)
《C#本质论》读书笔记(12)委托和Lambda表达式《C#本质论》读书笔记(12)委托和Lambda表达式 Type Expression represents." xml:space="preserve">获取此 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<int, int, bool>> 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. public static void PrintNode(Expression expression, int indent)

  25. {

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

  27. PrintNode(expression as BinaryExpression, indent);

  28. else

  29. PrintSingle(expression, indent);

  30. }

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

  32. {

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

  34. PrintSingle(expression, indent);

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

  36. }

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

  38. {

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

  40. }

  41. private static string NodeToSting(Expression expression)

  42. {

  43. switch (expression.NodeType)

  44. {

  45. case ExpressionType.Multiply:

  46. return "*";

  47. case ExpressionType.Add:

  48. return "+";

  49. case ExpressionType.Divide:

  50. return "/";

  51. case ExpressionType.Subtract:

  52. return "-";

  53. case ExpressionType.GreaterThan:

  54. return ">";

  55. case ExpressionType.LessThan:

  56. return "<";

  57. default:

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

  59. }

  60. }

《C#本质论》读书笔记(12)委托和Lambda表达式
证明表达式树是由零个或多个其他表达式树构成,代表Lambda的“根”通过Body属性引用Lambda的主体。每个表达式树节点都包含枚举类型ExpressionType的一个NodeType属性,描述它是哪一种表达式。

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