.NET中那些所谓的新语法之三:系统预约义委托与Lambda表达式

开篇:在上一篇中,咱们了解了匿名类、匿名方法与扩展方法等所谓的新语法,这一篇咱们继续征程,看看系统预约义委托(Action/Func/Predicate)和超爱的Lambda表达式。为了方便码农们,.Net基类库针对实际开发中最经常使用的情形提供了几个预约义好的委托,这些委托能够直接使用,无需再重头定义一个本身的委托类型。预约义委托在.Net基类库中使用的比较普遍,好比在Lambda表达式和并行计算中都大量地使用,须要咱们予以关注起来!html

/* 新语法索引 */

7.系统内置委托 Func / Action
8.Lambda表达式

  自 .NET Framework 3.5 (C# 3.0)以来,各类泛型委托纷涌而至,原先须要咱们程序员手动定义的一些委托如今咱们能够直接使用预约义的委托了,大大提升了开发效率,如今咱们就首先来看看这些预约义的泛型委托。程序员

1、无返回类型的内置委托—Action

1.1 初识Action

MSDN给出的定义: 封装一个方法,该方法不具备参数而且不返回值

  可使用此委托以参数形式传递方法,而不用显式声明自定义的委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法不得具备参数,而且不得返回值。(在 C# 中,该方法必须返回 void)一般,这种方法用于执行某个操做。数据库

  如今,咱们来看看如何使用Action委托:编程

  (1)先看看以前咱们是怎么来使用无返回值委托的例子:数组

public delegate void ShowValue();

public class Name
{
   private string instanceName;

   public Name(string name)
   {
      this.instanceName = name;
   }

   public void DisplayToConsole()
   {
      Console.WriteLine(this.instanceName);
   }

   public void DisplayToWindow()
   {
      MessageBox.Show(this.instanceName);
   }
}

public class testTestDelegate
{
   public static void Main()
   {
      Name testName = new Name("Koani");
      ShowValue showMethod = testName.DisplayToWindow;
      showMethod();
   }
}
View Code

  能够清楚地看出,咱们以前要先显式声明了一个名为 ShowValue 的委托,并将对 Name.DisplayToWindow 实例方法的引用分配给其委托实例。less

  (2)再看看有了Action委托以后咱们怎么来达到上面的效果的例子:编程语言

public class Name
{
   private string instanceName;

   public Name(string name)
   {
      this.instanceName = name;
   }

   public void DisplayToConsole()
   {
      Console.WriteLine(this.instanceName);
   }

   public void DisplayToWindow()
   {
      MessageBox.Show(this.instanceName);
   }
}

public class testTestDelegate
{
   public static void Main()
   {
      Name testName = new Name("Koani");
      Action showMethod = testName.DisplayToWindow;
      showMethod();
   }
}
View Code

  能够清楚地看出,如今使用 Action 委托时,没必要显式定义一个封装无参数过程的委托。ide

1.2 深刻Action

  在实际开发中,咱们常常将一个委托实例做为一个方法的参数进行传递,因而咱们来看一下这个典型的场景,再经过Reflector反编译工具查看编译器到底帮咱们作了什么好玩的事儿!函数式编程

  (1)首先来看一下在List集合类型的ForEach方法的定义:函数

        //
        // 摘要:
        //     对 System.Collections.Generic.List<T> 的每一个元素执行指定操做。
        //
        // 参数:
        //   action:
        //     要对 System.Collections.Generic.List<T> 的每一个元素执行的 System.Action<T> 委托。
        //
        // 异常:
        //   System.ArgumentNullException:
        //     action 为 null。
        public void ForEach(Action<T> action);

  能够看出,ForEach方法的参数是一个Action委托实例,也就是说是一个无返回值的委托实例。

  (2)定义一个实体类,并经过Action委托使用ForEach方法:

    public class Person
    {
        public int ID { get; set; }

        public string Name { get; set; }

        public int Age { get; set; }
    }

    static void ActionDelegateDemo()
    {
         List<Person> personList = GetPersonList();

         personList.ForEach(new Action<Person>(delegate(Person p)
         {
                Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
          }));
     }
View Code

  能够看出,咱们为ForEach方法传递了一个Action委托的实例,本质上是一个无返回值的方法指针,遍历输出了每一个Person对象的信息。

  

  (3)也许有些童鞋看到上面的仍是有点不解,只要你了解过委托,那么咱们能够经过Reflector反编译工具去看看编译器到底作了啥事,Action委托的本质就会一如了然:(这里咱们能够先看看没有Action的作法,是否是须要首先显式声明了一个无返回值的委托,而后是否是还要顶一个命名的无返回值的方法?

  ①将编译好的程序集拖动到Reflector中,能够看到如下的情形:

  ②如今分别看看编译器为咱们自动生成的无返回值的委托定义和方法定义:

  能够看出,不论是自动生成的委托仍是方法,都是不带返回值的。

  ③有了上面的分析,咱们再来看看执行的语句是怎么被编译的:

   能够看出,在编译后的代码里边连new Action<Person>()都省掉了,咱们也能够知道,在代码中能够更加简化。可是,首先,咱们得了解到底编译器是怎么识别Action委托的。因而,按照前两篇的思路,在反编译后的C#代码看不出什么端倪的时候,切换到IL代码一探究竟:

  由IL代码能够看出,仍是原来的方法,仍是原来的味道。委托仍是那个委托,执行委托仍是执行那个方法。这里,咱们再来看看List类型的ForEach方法是怎么使用Action委托的:

  如今,咱们能够知道,原来所不解的东西如今终于释怀了:在ForEach会经过一个循环遍历依次调用委托所持有的方法,这个方法是一个符合Action委托定义的无返回值方法。至于,为何咱们能够省略new Action<T>(),则是编译器为咱们提供的一个便利。例如,咱们在使用List<Person>对象的ForEach方法时,咱们能够这样写:

personList.ForEach(delegate(Person p)
{
      Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
});

  首先,因为咱们是使用的personList这个对象(List<Person>类型),因此编译器自动识别了泛型委托的T(即指定类型)为Person。其次,编译器自动将无返回值的匿名方法转换为了new Action<Person>对象。固然,若是是有返回值的匿名方法则会转换为指定类型的new Func<T>()对象,这里由于ForEach只接受无参数的委托实例或方法,因此若是传入了有返回值的匿名方法则会报错。

1.3 你究竟有几个Action可用?


  从图中能够看出,.NET Framework为咱们提供了多达16个参数的Action委托定义,对于常见的开发场景已经彻底够用了。

2、有返回类型的内置委托—Func

2.1 初识Func

MSDN给出的定义: 封装一个具备一个参数并返回 TResult 参数指定的类型值的方法

  此委托的定义以下:

public delegate TResult Func<in T, out TResult>(T arg)

  (1)in T :此委托封装的方法的参数类型。

  (2)out TResult :此委托封装的方法的返回值类型。

  可使用此委托表示一种能以参数形式传递的方法,而不用显式声明自定义委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具备一个经过值传递给它的参数,而且必须返回值。

  2.1.1 没有Func时的使用

delegate string ConvertMethod(string inString);

public class DelegateExample
{
   public static void Main()
   {
      ConvertMethod convertMeth = UppercaseString;
      string name = "Dakota";
      Console.WriteLine(convertMeth(name));
   }

   private static string UppercaseString(string inputString)
   {
      return inputString.ToUpper();
   }
}
View Code

  2.1.2 有了Func后的使用

public class GenericFunc
{
   public static void Main()
   {
      Func<string, string> convertMethod = UppercaseString;
      string name = "Dakota";

      Console.WriteLine(convertMethod(name));
   }

   private static string UppercaseString(string inputString)
   {
      return inputString.ToUpper();
   }
}
View Code

  固然,咱们还能够借助匿名方法更加便捷地使用:

public class Anonymous
{
   public static void Main()
   {
      Func<string, string> convert = delegate(string s)
         { return s.ToUpper();}; 

      string name = "Dakota";
      Console.WriteLine(convert(name));   
   }
}
View Code

  能够清楚地看出,如今使用 Func 委托时,没必要显式定义一个新委托并将命名方法分配给该委托。

2.2 深刻Func

  2.2.1 用法先行:爽一下

  咱们已经知道Func委托是带指定返回值类型的委托,那么咱们来看看在实际开发场景的一幕。仍是以刚刚那个数据集合PersonList为例,在不少时候咱们须要对从数据库中读取的数据集合进行二次筛选,这时咱们可使用List集合的Select方法,咱们将一个Func委托实例做为方法参数传递给Select方法,就能够返回一个符合咱们指定条件的新数据集合。

  (1)先来看看Select方法的定义:

        //
        // 摘要:
        //     将序列中的每一个元素投影到新表中。
        //
        // 参数:
        //   source:
        //     一个值序列,要对该序列调用转换函数。
        //
        //   selector:
        //     应用于每一个元素的转换函数。
        //
        // 类型参数:
        //   TSource:
        //     source 中的元素的类型。
        //
        //   TResult:
        //     selector 返回的值的类型。
        //
        // 返回结果:
        //     一个 System.Collections.Generic.IEnumerable<T>,其元素为对 source 的每一个元素调用转换函数的结果。
        //
        // 异常:
        //   System.ArgumentNullException:
        //     source 或 selector 为 null。
        public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);

  能够看出,Select方法中的参数采用了Func泛型委托,根据泛型委托的定义TSource和TResult分别表明要传入的数据类型以及要返回的数据类型。

  (2)再来看看如何在程序中使用Func委托:

  首先定义一个与源数据类型不一样的新数据类型做为返回值类型:

    public class LitePerson
    {
        public string Name { get; set; }
    }

  ①标准定义版:

            List<Person> personList = GetPersonList();
            
            IEnumerable<LitePerson> litePersonList = personList.Select<Person, LitePerson>(
                new Func<Person, LitePerson>
                (
                    delegate(Person p)
                    {
                        return new LitePerson() { Name = p.Name };
                    }
                )
            );    

  ②嘻哈简化版:借助编译器提供的自动识别,简化咱们的代码

            IEnumerable<LitePerson> litePersonList = personList.Select(
                delegate(Person p)
                {
                    return new LitePerson() { Name = p.Name };
                }
            );

  ③绝逼懒人版:借助匿名类和泛型能够大大简化咱们的代码

            var liteList = personList.Select(delegate(Person p)
            {
                return new { Name = p.Name, AddDate = DateTime.Now };
            });

  (3)调试运行能够获得如下结果:

  2.2.2 原理为王:探一次

  (1)经过Reflector反编译,咱们再来看看编译器帮咱们生成的东东:

  (2)看看自动生成的委托和方法的定义:

  相信通过上节Action的详细分析,这里你们应该也能够触类旁通了解编译器帮咱们到底作了什么事儿了,这里我就再也不赘述了,后面也不会再赘述此方面的东东(为了节省页面大小)。

  固然,和Action相似,.NET基类库为咱们也提供了多达16个输入参数的Func委托,可是,输出参数却只有1个。

3、返回bool类型的内置委托—Predicate

3.1 初识Predicate

  通过了Func的了解,咱们能够知道接下来的这两个Predicate和Comparison其实都属于有返回值类型的委托,他们不过是两个具体的特殊实例而已(一个返回bool类型,一个返回int类型)。

MSDN给出的定义: 表示定义一组条件并肯定指定对象是否符合这些条件的方法

  它的定义很简单:(这里就再也不对其进行解释了)

public delegate bool Predicate<in T>(T obj)

  此委托由 Array 和 List<T> 类的几种方法使用,经常使用于在集合中搜索元素。

3.2 深刻Predicate

  因为Predicate委托经常使用于在集合中搜索元素,那么咱们就来看看如何使用Predicate委托来进行元素的搜索。因而,咱们将目光转到List集合的FindAll方法,相信大部分童鞋都用过这个方法。

  (1)先来看看FindAll的定义:

        //
        // 摘要:
        //     检索与指定谓词定义的条件匹配的全部元素。
        //
        // 参数:
        //   match:
        //     System.Predicate<T> 委托,用于定义要搜索的元素应知足的条件。
        //
        // 返回结果:
        //     若是找到,则为一个 System.Collections.Generic.List<T>,其中包含与指定谓词所定义的条件相匹配的全部元素;不然为一个空
        //     System.Collections.Generic.List<T>。
        //
        // 异常:
        //   System.ArgumentNullException:
        //     match 为 null。
        public List<T> FindAll(Predicate<T> match);

  (2)再来看看FindAll的实现:

  (3)如今咱们来用一下Predicate委托:仍是以那个PersonList集合为例,假如咱们要筛选出Age>20的Person,咱们就可使用FindAll方法。如今咱们来写一下这个委托:(后面咱们会用Lambda表达式来简写,那才叫一个爽!)能够看出,关键点在于:delegate(Person p) { return p.Age > 20; }这一句上,传入参数是Person类型的对象,返回的是一个比较结果即bool值。

            List<Person> personList = GetPersonList();

            List<Person> agedList = personList.FindAll(
                new Predicate<Person>(delegate(Person p) 
                    { 
                        return p.Age > 20; 
                    }
                )
            );
View Code

4、返回int类型的内置委托—Comparison

4.1 初识Comparison

MSDN给出的定义:表示比较同一类型的两个对象的方法

  它的定义也很简单:

public delegate int Comparison<in T>(T x, T y)

  T是要比较的对象的类型,而返回值是一个有符号整数,指示 x 与 y 的相对值,以下表所示:

含义

小于 0

x 小于 y。

0

x 等于 y。

大于 0

x 大于 y。

  此委托由 Array 类的 Sort<T>(T[], Comparison<T>) 方法重载和 List<T> 类的 Sort(Comparison<T>) 方法重载使用,用于对数组或列表中的元素进行排序

4.2 深刻Comparison

  因为Comparison委托经常使用于在集合中进行排序,那么咱们就来看看如何使用Comparison委托来进行元素的排序。因而,咱们将目光转到List集合的Sort方法,相信大部分童鞋也都用过这个方法。

  (1)老惯例,仍是先看看Sort方法的定义:

        //
        // 摘要:
        //     使用指定的 System.Comparison<T> 对整个 System.Collections.Generic.List<T> 中的元素进行排序。
        //
        // 参数:
        //   comparison:
        //     比较元素时要使用的 System.Comparison<T>。
        //
        // 异常:
        //   System.ArgumentNullException:
        //     comparison 为 null。
        //
        //   System.ArgumentException:
        //     在排序过程当中,comparison 的实现会致使错误。 例如,将某个项与其自身进行比较时,comparison 可能不返回 0。
        public void Sort(Comparison<T> comparison);

  (2)再来看看Sort方法的实现:

  能够看出,这里虽然使用Comparison委托但最终仍是转换成了Comparer比较器,再次调用重载的Array.Sort静态方法进行排序。

  (3)如今咱们来用一下Comparison委托:仍是以那个PersonList集合为例,假如咱们要以Age为条件进行降序排列,咱们应该怎么来写这个委托呢?

List<Person> personList = GetPersonList();

personList.Sort(delegate(Person p1, Person p2)
{
      return p2.Age - p1.Age;
});

personList.ForEach(delegate(Person p)
{
      Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
});
View Code

  实现的效果以下图所示:

  那么,若是是要进行升序排列呢?只须要改一下:return p2.Age-p1.Age; 更改一下被减数和减数的位置,便可完成升序和降序的切换。

personList.Sort(delegate(Person p1, Person p2)
{
      return p1.Age - p2.Age;
});
View Code

5、Lambda表达式:[ C# 3.0/.NET 3.x 新增特性 ]

  回顾,发现上面的代码,须要传一个 匿名方法 ,写起来特别别扭。因而咱们很想知道可否有简化的语法呢?微软告诉我们:Of Course,必须有,它就是Lambda表达式。Lambda表达式是比匿名方法更简洁的一种匿名方法语法。

Lambda来源:1920年到1930年期间,数学家Alonzo Church等人发明了Lambda积分。Lambda积分是用于表示函数的一套系统,它使用希腊字母Lambda(λ)来表示无名函数。近年来,函数式编程语言(如Lisp)使用这个术语来表示能够直接描述函数定义的表达式,表达式再也不须要有名字了。

5.1 初识Lambda表达式lambda  5.1.1 Lambda表达式要点

    ①Lambda表达式中的参数列表(参数数量、类型和位置)必须与委托相匹配

    ②表达式中的参数列表不必定须要包含类型,除非委托有ref或out关键字(此时必须显示声明);

    ③若是没有参数,必须使用一组空的圆括号

  5.1.2 Lambda使用示例

        static void LambdaDemo()
        {
            List<Person> personList = GetPersonList();
            Console.WriteLine("--------------------标准预约义委托--------------------");
            Console.WriteLine("Standard Action Delegate Show:");
            personList.ForEach(new Action<Person>(delegate(Person p)
                {
                    Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
                })
            );

            Console.WriteLine("Simple Action Delegate Show:");
            personList.ForEach(delegate(Person p)
            {
                Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
            });
            Console.WriteLine("--------------------Lambda表达式--------------------");
            Console.WriteLine("Lambda Expression Show:");
            personList.ForEach(p =>
                Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age));

            Console.WriteLine("FindAll:");
            var dataList = personList.FindAll(p => p.Age > 20);
            foreach (var item in dataList)
            {
                Console.WriteLine(item.ID + "-" + item.Name + "-" + item.Age);
            }

            Console.WriteLine("Sort:");
            personList.Sort((p1, p2) => p1.Age - p2.Age);

            Console.WriteLine("Select:");
            var selectList = personList.Select(p => new LitePerson() { Name = p.Name });
            foreach(var item in selectList)
            {
                Console.WriteLine(item.Name);
            }
        }
View Code

   调试运行的结果以下:

  5.1.3 Lambda本质探析

  (1)以上述案例中的Sort方法为例:personList.Sort((p1, p2) => p1.Age - p2.Age);

  (2)经过反编译工具,能够看到实际上是声明了一个Comparison委托实例:

  (3)如今,咱们来分析一下具体的步凑:有了前面的基础,如今再来看就轻松了许多,So Easy!

    ①编译器自动生成了一个Comparison委托:

    ②编译器帮咱们建立了一个符合Comparison委托签名的静态方法:

    ③实例化Comparison委托变量,并将方法指针传入该委托;

    ④调用List<T>实例的Sort方法,并传入Comparison委托实例;

    其中,前面两步①和②能够经过反编译后的C#代码获知,然后面两步③和④则须要经过IL代码来分析,前面已经介绍过相关,这里就再也不赘述。

5.2 回顾Lambda进化史

  前面了解了Lambda是什么,这里咱们来回顾一下Lambda的演化过程。

  从演化过程能够知道,编译器在愈来愈智能地帮咱们作着更多的事儿,而咱们却在享受着编译器带来的便利沉浸在高效的开发效率中,变得愈来愈“懒”了。

5.3 语句Lambda

  Lambda表达式有两种类型:一是Lambda表达式,二是语句Lambda。

  那么,语句Lambda和表达式Lambda到底有何区别?

ANSWER:语句Lambda 和 表达式Lambda 的区别在于,前者在 =>右边有一个语句块(大括号),然后者只有一个表达式(没有return 和大括号)。
EXAMPLES:
(1)表达式Lambda:
list.FindAll(d => d.Id > 2);// goes to
list.ForEach(d => Response.Write(d.ToString() + "<br/>"));

(2)语句Lambda:

list.ForEach(d => { if (d.Id > 2) { Response.Write(d.ToString() + "<br/>"); } });

  能够看出,语句Lambda的右侧有一个语句块,在这个大括号内的语句可能会有多条。

参考文章

  (1)金旭亮,《C#面向对象程序设计》,教案6-委托与事件讲义:http://download.csdn.net/detail/bitfan/3324733

  (2)MSDN,泛型委托(C#编程指南):http://msdn.microsoft.com/zh-cn/library/sx2bwtw7.aspx

  (3)min,《泛型委托在项目中的应用》:http://www.cnblogs.com/ASPNET2008/archive/2010/04/05/1704405.html

  (4)MSDN,Lambda表达式(C#编程指南):http://msdn.microsoft.com/zh-cn/library/bb397687.aspx

  (5)张龙豪,《Lambda表达式详解》:http://www.cnblogs.com/knowledgesea/p/3163725.html

附件下载

  NewGrammerDemos v1.2:http://pan.baidu.com/s/1gdxi39D

 

相关文章
相关标签/搜索