zoukankan      html  css  js  c++  java
  • 十二、C# 委托与Lambda表达式(匿名方法的另一种写法)

    委托与Lambda表达式
     
    1、委托概述
    2、匿名方法
    3、语句Lambda
    4、表达式Lambda
    5、表达式树
     
    一、委托概述
    相当于C++当中的方法指针,在C#中使用delegate 委托来提供相同的功能,
    它将方法作为对象封装起来,允许在"运行时"间接地绑定一个方法调用。
    声明的委托相当于一种自定义的数据类型。
    1、背景
    冒泡排序
     1     static class SimpleSort1
     2     {
     3         public static void BubbleSort(int[] items)
     4         {
     5             int i = 0, j = 0, temp = 0;
     6             if (items == null)
     7             {
     8                 return;
     9             }
    10             for (i = items.Length - 1; i >= 0; i--)
    11             {
    12                 for (j = 1; j <= i; j++)
    13                 {
    14                     if (items[j - 1] > items[i])
    15                     {
    16                         temp = items[j - 1];
    17                         items[j - 1] = items[i];
    18                         items[i] = temp;
    19                     }
    20                 }
    21  
    22             }
    23         }
    24     }
    如果需要提供升序和降序两个功能选择,可以加上参数SortType来区别
     1     class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5  
     6             int[] arr = new int[] { 1, 2, 5, 645, 4, 6, 73, 5, 3, 6, 7, 66 };
     7             SimpleSort1.BubbleSort(arr, SortType.Ascending);
     8             string str = "";
     9             for (int i = 0; i < arr.Length; i++)
    10             {
    11                 str += arr[i] + ",";
    12  
    13             }
    14             Console.WriteLine(str);
    15  
    16             str = "";
    17             SimpleSort1.BubbleSort(arr, SortType.Descending);
    18             for (int i = 0; i < arr.Length; i++)
    19             {
    20                 str += arr[i] + ",";
    21  
    22             }
    23             Console.WriteLine(str);
    24             Console.ReadLine();
    25  
    26         }
    27     }
    28     enum SortType
    29     {
    30         Ascending,
    31         Descending
    32     }
    33     static class SimpleSort1
    34     {
    35         public static void BubbleSort(int[] items, SortType sorttype)
    36         {
    37             int i = 0, j = 0, temp = 0;
    38             if (items == null)
    39             {
    40                 return;
    41             }
    42             for (i = items.Length - 1; i >= 0; i--)
    43             {
    44                 for (j = 1; j <= i; j++)
    45                 {
    46                     switch (sorttype)
    47                     {
    48                         case SortType.Ascending:
    49                             if (items[j - 1] > items[i])
    50                             {
    51                                 temp = items[j - 1];
    52                                 items[j - 1] = items[i];
    53                                 items[i] = temp;
    54                             }
    55                             break;
    56                         case SortType.Descending:
    57                             if (items[j - 1] < items[i])
    58                             {
    59                                 temp = items[j - 1];
    60                                 items[j - 1] = items[i];
    61                                 items[i] = temp;
    62                             }
    63                             break;
    64  
    65                     }
    66  
    67                 }
    68  
    69             }
    70  
    71  
    72         }
    73     }
    74  
    如果想要按字母、随机或者按照其他方式来排序, 可以通过增加一个SortType参数
    BubbleSort()方法及其对应的SortType值的数量很快会变量非常之多。
     
    2、委托数据类型
    为了减少重复代码的数量,可以将比较方法作一个参数
    传给BubbleSort()方法,此外,为了能将方法作为参数传递,必须要有一个能够
    表示方法的数据类型---也就是要有一个委托。
     
    大致使用步骤:声明一个委托数据类型,声明的一个新的委托相当于一个数据类型。
    可以用来作为形式参数的类型,在方法调用的时候,赋值一个与此委托相当签名的方法。
     
     
    3、委托的内部机制
    C#将所有委托定义成间接派生于System.Delegate
    委托类型的对象模型:待查。
    所有委托都是不可变的。更改“委托”要求实例化一个新委托,并在新委托中包含要修改
    的地方。
     
    4、委托类型的定义
     1   class DelegateSample
     2     {
     3         public delegate bool ComparisonHandler(int first, int second);
     4         //相当于创建了一个数据类型:DelegateSample.ComparisonHandler
     5 //因为它被定义成嵌套在DelegateSample中的一个类型。
     6  
     7     }
     8 虽然所有委托数据类型都间接从System.Delegate派生,但C#编译器并不允许定义一个直接或间接
     9 从System.Delegate派生的类。
    10  
    11     class Program
    12     {
    13         static void Main(string[] args)
    14         {
    15  
    16             int[] arr = new int[] { 1, 32, 5, 645, 4, 6, 73, 5, 36, 61, 7, 66 };
    17             string str = "";
    18             //调用方法时,将指定的函数作为实际参数使用。使用方法来创建一个委托变量,委托是一个引用类型,但不必
    19             //用new来实例化它。直接传递名称,而不是显式实例化,这是自C#2.0开始支持的一个新语法,称为委托推断 delegate interface
    20             //采用这个语法,编译器将根据方法名来查找方法签名,并验证它同方法的参数类型匹配。
    21             SimpleSort1.BubbleSort(arr, SimpleSort1.GreaterThan);          
    22             for (int i = 0; i < arr.Length; i++)
    23             {
    24                 str += arr[i] + ",";
    25  
    26             }
    27             Console.WriteLine(str);
    28  
    29             str = "";
    30             SimpleSort1.BubbleSort(arr, SimpleSort1.LonwerThan);
    31             for (int i = 0; i < arr.Length; i++)
    32             {
    33                 str += arr[i] + ",";
    34  
    35             }
    36             Console.WriteLine(str);
    37  
    38             str = "";
    39             SimpleSort1.BubbleSort(arr, SimpleSort1.CharThan);
    40             for (int i = 0; i < arr.Length; i++)
    41             {
    42                 str += arr[i] + ",";
    43  
    44             }
    45             Console.WriteLine(str);
    46  
    47             Console.ReadLine();
    48  
    49  
    50  
    51         }
    52     }
    53  
    54     static class SimpleSort1
    55     {
    56         //使用委托数据类型 声明一个变量作为形式参数
    57         public static void BubbleSort(int[] items, DelegateSample.ComparisonHandler compareMethod)
    58         {
    59             int i = 0, j = 0, temp = 0;
    60             if (items == null)
    61             {
    62                 return;
    63             }
    64             for (i = items.Length - 1; i >= 0; i--)
    65             {
    66                 for (j = 1; j <= i; j++)
    67                 {
    68                     if (compareMethod(items[j - 1], items[i]))
    69                     {
    70                         temp = items[j - 1];
    71                         items[j - 1] = items[i];
    72                         items[i] = temp;
    73                     }
    74                 }
    75             }
    76         }
    77  
    78  
    79         //以下四个函数都与数据类型DelegateSample.ComparisonHandler(委托) 具有同样的签名
    80         public static bool GreaterThan(int first, int second)
    81         {
    82             return first > second;
    83         }
    84         public static bool LonwerThan(int first, int second)
    85         {
    86             return first < second;
    87         }
    88         public static bool CharThan(int first, int second)
    89         {
    90             int flag = (first.ToString()).CompareTo(second.ToString());
    91             return (flag > 0) ? true : false;
    92         }
    93     }
    94     class DelegateSample
    95     {
    96         public delegate bool ComparisonHandler(int first, int second);
    97         //相当于创建了一个数据类型:DelegateSample.ComparisonHandler
    98  
    99     }
     
    二、匿名方法
    C#2.0 和更高的版本还支持不念旧恶名为匿名方法的特性。
    所谓匿名方法,就是没有实际方法声明的委托实例。
    1             DelegateSample.ComparisonHandler compareMethod;
    2             compareMethod =
    3                 delegate(int first, int second)
    4                 {
    5                     return first > second;
    6                 };
    7             SimpleSort1.BubbleSort(arr, compareMethod);
     
    在这个上下文中,delegate关键字指定了一个“委托字面值“类型,这类似用双引号来指定一个
    字符串字面值。
    同理可以在不使用compareMethod 变量的前提下直接使用匿名方法当作实际参数来使用。
    1             SimpleSort1.BubbleSort(arr,
    2                 delegate(int first, int second)
    3                 {
    4                     return first > second;
    5                 }
    6                 );
    在任何情况下,匿名方法的参数和返回值类型都必须兼容于相对应的委托类型。
    自C#2.0开始,可以利用匿名方法这一新特性来声明一个没有名字的方法,该方法将
    自动被转换成一个委托。
    无参数的匿名方法,可以省略() ,但返回类型仍然需要与委托的返回类型兼容。
     
    三、系统定义的委托:Func<> Action<>
    在.NET3.5(C#3.0)中,存在一系列名为Action和Func的泛型委托。(委托模板)
    System.Func代表有返回类型的委托,而System.Action代表无返回类型的委托。
    这些委托全是泛型的,因此可以直接使用它们而不用自定义一个委托。
    如:
    系统自带的
    public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);这只是其中的一种
    更多可以参考源程序集。
    可以如此使用
    1             System.Func<int, int, bool> compareMethodFun;
    2             compareMethodFun =
    3                 delegate(int first, int second)
    4                 {
    5                     return first > second;
    6                 };
     
    代码可以不必声明一个ComparisonHandler委托类型,而是直接使用Func<int, int, bool> 这个泛型类型委托
    来声明一个委托类型的实例。
    注:Func的最后一个类型参数总是委托的返回类型,在它之前的类型参数
    则依次对应于委托参数的类型。
    对于那些没有返回类型而且不接受参数的委托,应该使用System.Action或者某个泛型类型。
     
     
    比如自定义的委托类型,可以让我们明确知道该委托的用途,相反
    Func<int, int, bool> 除了让我们明白方法的签名之外,就不能再提供其他有意义的信息了。
     
    注:这些只有安装.NET3.5的客户端才能使用此功能。
     
    注意,即使可以且一个Func泛型委托来代替一个显式定义的委托,两者的类型也不兼容。
    不能将一个委托类型赋给另一个委托类型的变量,即使类型参数匹配。
     
    通过C#4.0 添加的对协变性和逆变性的支持,确实可以在它们之间进行一定程序的转型。
     
    但是,假如代码中使用某个委托类型能使编码变得更简单,那么仍然应该声明委托类型。
     
    void Action<in T>(T arg) 有一个in类型的参数修饰符,所以支持逆变性
    所以可以将Action<object>类型的一个对象赋给Action<string>类型的一个变量。
    同理out
     
     1             Action<Object> broadAction = delegate(Object o)
     2             {
     3                 Console.WriteLine(o);
     4             };
     5             Action<String> narrowAction = broadAction;
     6  
     7             Func<string> narrowFunction = delegate()
     8             {
     9                 return Console.ReadLine();
    10             };
    11  
    12             Func<Object> broadFunction = narrowFunction;
    同理,可以in out同时发生。
    1               Func<Object, String> narrowFunction = delegate(Object obj)
    2             {
    3  
    4                 return obj.ToString();
    5             };
    6  
    7             Func<String, Object> broadFunction = narrowFunction;
    在这些泛型委托中对协变性和逆变性 的需求是C#现在添加这个功能的一个关键原因。
     
    四、Lambda表达式
    C#3.0中引入的Lambda表达式是比匿名方法更加简洁的一种匿名函数语法。
    这里的匿名函数泛指Lambda表达式和匿名方法。
    Lambda表达式本身划分为两种类型:语句Lambda和表达式Lambda。
    与匿名函数有关的术语,待查。
    注:所有匿名函数都是不可变的。
     
    1、语句Lambda
    语句Lambda是C#3.0为匿名方法提供的一种简化语法。
    这种语法不包含delegate关键字,但添加了Lambda运算符  =>  。
     
    1             SimpleSort1.BubbleSort(arr,
    2                 (int first, int second) =>
    3                 {
    4       //可以有多个语句
    5                     return first > second;
    6                 }
    7                 );
    查看含有Lambda运算符的代码时,可以将这个运算符理解成"用于"两字。
    如以上则理解为first second用于返回 first>second。
    语句Lambda还允许通过"类型参数推断"来进一步简化语法。可以不显式声明参数的
    数据类型,只要编译器能推断出类型,语句Lambda就允许省略参数类型。
    1             SimpleSort1.BubbleSort(arr,
    2                 (first, second) =>
    3                 {
    4                     return first > second;
    5                 }
    6                 );
    通常,只要编译器能推断出参数类型,或者能将参数类型隐式转换成期望的类型,语句
    Lambda就不需要参数类型。
    只要语句Lambda包含了一个类型,所有类型都要加上。
    即使是无参数的语句Lambda,也需要输入一对空白的圆括号。
     
    圆括号规则的一个例外是,如果编译器能推断出数据类型,而且只有一个输入参数的时候,语句
    Lambda可以不带圆括号。
    如:
    1             IEnumerable<Process> processes = Process.GetProcesses().Where(
    2                 process => { return process.WorkingSet64 > (2 ^ 30); });
     
    以上代码返回的是对物理内存占用超过1GB的进程的一个查询。
    虽然一个语句Lambda允许包含任意数量的语句,但在它的语句块中,一般只使用两三条语句。
     
    2、表达式Lambda
    语句Lambda含有一个语句块,所以可以包含多个语句或者零个语句。
    表达式Lambda只有一个表达式,没有语句块。其它一致。
    如:
    1             SimpleSort1.BubbleSort(arr, (first, second) => first > second);
    2             SimpleSort1.BubbleSort(arr, (int first, int second) => first > second);
    语句Lambda和表达式Lambda的区别在于,语句Lambda在Lambda运算符的右侧有一个语句块,用{}包含。
    而表达式Lambda只有一个表达式(没有return语句及大括号)。
     
    注:括号及参数类型的省略同语句Lambda
     
    3、Lambda表达式和匿名方法的内部机制
     
    Lambda表达式(和匿名方法)并非CLR内部的固有构造,它们的实现是C#编译器在编译时生成的。
     
    Lambda表达式为“以内嵌方式声明的委托”模式提供了一个对应的C#语言构造。
    这样一来,C#编译就会为这个模式生成实现代码。
    在CIL中,一个匿名方法被转换成一个单独、由编译器
    内部声明的静态方法,这个静态方法再实例化成一个委托,并作为参数传递。
     
    4、外部变量
    在Lambda表达式(包括参数)的外部声明,但在Lambda表达式内部访问的局部变量称为该
    Lambda表达式的外部变量。
    this也是一个外部变量,外部变量被匿名函数捕捉(访问)后,在匿名函数的委托被销毁之前,该
    外部变量将一直存在。
    被捕捉的变量的生存期至少和捕捉它的最长命的委托对象一样长。
    类似闭包。
     
    外部变量的CIL实现 。
    被捕捉的局部变量被当作一个实例字段来实现,从而延长了其生命周期。
    对局部变量的所有引用都被重新编写成对那个字段的引用。
    变量允许在不改变表达式签名的前提下,将数据从表达戒指一次调用传递到下一次调用。
     
    5、表达式树
     
    如果一种Lambda表达式代表的是与表达式有关的数据,而不是编译好的代码,这种Lambda表达式就是
    "表达式树"。
    1、Lambda表达式作为数据使用
    由于表达式树代表的是数据而非编译好的代码,所以可以把数据转换成一种替代格式。
    例如,可以把它从表达式数据转换成数据库中执行的SQL代码。
    但,表达式树并非只能转换成SQL语句。
     
    2、表达式树作为对象图使用
    表达式树转换成的数据是一个对象图,它由System.Linq.Expressions.Expression表示。
     
     
    3、Lambda表达式和表达式树的比较
     
    在编译时,Lambda表达式在CIL中被编译成一个委托,而表达式树被编译成System.Linq.Expressions.Expression
    类型的一个数据结构。
     
    可通过System.Linq.Enumerable 与 System.Linq.Queryable这两个类的进行比较.
    它们分别实现了IEnumerable和IQueryable集合接口。例如 Where()。
     
    4、解析表达式树
    由于Lambda表达式没有与生俱来的类型,所以将Lambda表达式赋给System.Linq.Expressions.Expression<TDelegate>
    会创建表达式树,而不是委托。
    1  System.Linq.Expressions.Expression<Func<int, int, bool>> expression;
    2 expression = (x, y) => x > y;
     
    表达式树是一个数据集合,而通过遍历数据,就可以将数据转换成另一种格式。
    有多个表达式类型,
    如:
    System.Linq.Expressions.BinaryExpression
    System.Linq.Expressions.ConditionalExpression
    System.Linq.Expressions.LambdaExpression
    System.Linq.Expressions.MethodCallExpression
    System.Linq.Expressions.ParameterExpression
    System.Linq.Expressions.ConstantExpression
    每个类都派生自System.Linq.Expressions.Expression
     
    通常,语句Lambda和表达式Lambda可以互换使用。然则,不能将语句Lambda转换成"表达式树"。
    只能使用表达式Lambda语法来表示"表达式树"。
     
    小结:
    利用委托,可以传递一组能在不同位置调用的指令,这些指令不是立即调用的,而是在完成编码之后,再在其他位置调用。
    C#2.0 中引入了"匿名方法"
    C#3.0 中引入了Lambda表达式的概念。
    Lambda表达式语法取代(但并非消除)了C#2.0 的匿名语法。
    不管哪种语法,程序员都可以利用它们直接将一组指令赋一个变量,而不必显式地定义
    一个包含这些指令的方法。
    这使得程序员可以在方法内部动态地编写指令----这是一个很强大的概念,
    它通过一个称为LINQ(语言集成查询)的API大幅简化了集合编程。
    表达式树的概念,描述了它们如何编译成数据来表示一个Lambda表达式,而不是表示委托的具体实现。
    利用这一特性,LINQ to SQL 和 LINQ to XML等库能解释表达式树,
    并在CIL之外的上下文中使用它。
     
  • 相关阅读:
    Object.assign
    js获取 some方法索引值
    Vue配置sass
    spring MVC,controller中获得resuqest和response的方式
    CentOS7中启动Tomcat后,8080端口不能被外部访问的解决办法。
    spring mvc 中 controller 路径配置
    Spring扫面路径配置不全导致异常 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): 的原因
    CentOS7中安装MySQL5.7
    eclipse maven web
    用Eclipse进行远程Debug代码
  • 原文地址:https://www.cnblogs.com/tlxxm/p/4620050.html
Copyright © 2011-2022 走看看