C# 6.0本质论委托和Lambda表达式

十二、委托和Lambda表达式

12.1 委托

12.1.1 基本概念

  • JAVA中的回调
    • 给方法传递对象,并在方法中调用对象的方法,该对象通常是接口的实现类的实例
  • 委托
    • 提供对方法的引用

12.1.2 声明

  • 使用delegate修饰符修饰委托类型名,其他和方法一样
    • 例如,public delegate bool ComparisonHandler(int first, int second);

12.1.3 实例化

  • 定义和实现符合委托声明的方法
    • 例如,public static bool GreaterThan(int first, int second){ return first> second; }
  • 实例化委托
    • 将方法名作为委托类型的实参传递,C#2.0会自动创建委托对象
      • C#1.0需要使用new ComparisonHandler(方法名)创建委托对象
    • 例如,BubbleSort(items, GreaterThan);

12.2 匿名函数

  • 没有方法名称,直接在需要传递方法时对方法进行定义,而不是单独定义
  • 匿名函数只是可以转化为委托类型的函数,但本质不是委托类型
  • 作用
    • 实例化委托过程中,简化(对符合委托声明的)方法的定义和实现
  • 匿名函数在CIL中的代码和未简化时的委托实现的方法代码类似
    • 即,编译器自动为匿名函数声明和实现一个符合委托类型要求的方法

12.2.1 匿名方法C#2.0

  • delegate(…) { … }
    • 必须保留参数类型和语句块
    • 必须在参数列表前用delegate修饰
    • 其他和Lambda表达式类似
    • 例如,BubbleSort (items, delegate(int first, int second){ return first < second });
  • 无参的匿名方法
    • 当方法主体中不使用任何参数,且委托类型只要求值参数时可以省略参数列表
    • 根据返回值匹配委托类型
    • 较少使用

12.2.2 Lambda表达式

  • Lambda来源于lambda calculus演算系统

12.2.2.1 语句Lambda

  • 只保留参数名称、参数类型和方法体,其他自动根据所需的委托类型以及委托声明推断
    • 例如,BubbleSort (items, (int first, int second) => { return first < second });
  • 参数类型也可省略
    • 若只有一个参数,且类型省略,可以不写 ()
      • process =>{ return process.WorkingSet64>1000000; }
    • 若不含参数或包含多个参数,则括号不能省略
      • (…) => { … }

12.2.2.2 表达式Lambda

  • 当语句Lambda中只有一个表达式时,可以不使用语句块,而直接使用表达式作为方法主体
    • () => expression

12.3 通用委托

  • 由运行时库定义的泛型委托
  • 作用
    • 避免自定义委托类型

12.3.1 通用委托分类

12.3.1.1 System.Func<>系列

  • 包含一组类似于 public delegate void Func<in T1, int T2, … , out TResult>(); 的通用委托
  • 有返回值
  • 参数列表中的最后一个作为返回值
  • 例如,BubbleSort( int[] items, ComparisonHandler handler )可以改写为 BubbleSort( int[] items, System.Func<int, int, bool> )

12.3.1.2 System.Action<>系列

  • 包含一组类似于 public delegate void Action<in T1, in T2, …>(); 的通用委托
  • 无返回值

12.4 委托的引用转换

  • 委托不具备结构相等性
    • 即使形参和返回值类型完全相同,也不能用一个委托类型指向另一个委托类型
    • 例如,不能将一个ComparisonHandler引用直接赋给一个Func<int, int, bool>变量
  • 可以创建一个新委托,让它引用旧委托的Invoke方法
    • 例如,Func<int, int, bool> f = c.Invoke ;
  • C#4.0的协变和逆变性支持部分委托类型的转换
    • 可以在两个类型参数具有派生关系的委托类型中进行转换
    • System.Action
      • 由于用 in 修饰类型参数,所有支持逆变性
    • System.Func
      • 同时支持协变性和逆变性,其中输入参数只支持逆变性,返回参数只支持协变性
Action<object> broadAction = (object data)
	=>{ Console.WriteLine(data); };
Action<string> narrowAction = broadAction;

12.5 外部变量

12.5.1 基本概念

  • 在Lambda表达式外部声明的变量
  • 被Lambda表达式捕捉后的变量,其生命周期至少和委托对象一样长,CIL中自动提升
  • Lambda表达式捕获外部变量,形成闭包

12.5.2 包含了外部变量的Lambda表达式的CIL实现

  • 编译器将为捕获了外部变量的Lambda表达式自动生成一个类
    • 类的结构
      • 实例字段
        • 即捕获的外部变量
      • 实例方法
        • 即Lambda表达式
  • 在传入Lambda表达式之前,对该类进行实例化,字段初始化为外部变量的值
    • 外部变量有多少个,闭包就有多少个
  • 在需要传入Lambda表达式的地方,都替换为实例化的类的成员方法名
  • 所有使用外部变量的地方,都重写为实例化之后的闭包类的字段
static void Main(string[] args)
{
    IList<Action> list = new List<Action>();
    for (int i = 0; i < 5; i++)
    {
        list.Add(() => { Console.WriteLine(i); });
    }
    foreach (var item in list)
    {
        item();
    }
    Console.Read();
}

static void Main(string[] args)
{
    IList<Action> list = new List<Action>();
    for (int i = 0; i < 5; i++)
    {
        int tmp = i;
        list.Add(() => { Console.WriteLine(tmp); });
    }
    foreach (var item in list)
    {
        item();
    }
    Console.Read();
}

public class ClosureBag
{
    public int externVariant;

    public void Lambda()
    {
        Console.WriteLine(this.externVariant);
    }
}
 
static void Main(string[] args)
{
    IList<Action> list = new List<Action>();
    ClosureBag closureBag= new ClosureBag();
    
    for (closureBag.externVariant = 0; closureBag.externVariant < 5; closureBag.externVariant++)
    {
        list.Add(closureBag.Lambda);
    }
    foreach (var item in list)
    {
        item();
    }
    Console.Read();
}
static void Main(string[] args)
{
    IList<Action> list = new List<Action>();
    for (int i = 0; i < 5; i++)
    {
        ClosureBag closureBag = new ClosureBag();
        closureBag.externVariant = i;
        list.Add(closureBag.Lambda);
    }
    foreach (var item in list)
    {
        item();
    }
    Console.Read();
}

12.5.3 捕捉了循环变量的Lambda表达式

  • foreach
    • C#5.0以后,捕捉foreach的循环变量会自动认为是不同的变量
    • C#5.0之前,读取外部变量最新的数据
  • for循环
    • 始终读取外部变量最新的数据
  • 解决方式
    • 可以在每次循环中声明一个新的局部变量去接收循环变量的值
class DoNotCaptureLoop
{
	static void Main()
	{
		var items = new string[] {"Moe","Larry","Curly"};
		var actions = new List<Action>();
		foreach(string item in items)
		{
			string _item = item;
			actions.Add(()=>{Console.WriteLine(_item);});
		}
		foreach(Action action in actions)
		{
			action();
		}
	}
}

12.6 表达式树类型

  • 一种数据结构,将Lambda表达式中的表达式Lambda提取成树的结构
  • 只有表达式Lambda才能转换为表达式树,语句Lambda不能转换为表达式树

12.6.1 表达式树的作用

  • 作为数据使用
    • 提取的树结构可以转化成其他查询语言,如SQL
  • 作为对象图
    • 包含参数、返回值类型、主体表达式

12.6.2 委托和表达式树的比较

  • Lambda表达式可以自动转化为委托类型或者表达式树
    • 委托类型
      • 方法的引用
        • 对于persons.Where( person => person.Name.ToUpper == “INIGO MONTOYA”);而言
        • 若转换为委托,则是方法的调用,persons是从数据库获取所有数据
    • 表达式树
      • 可以转换为SQL
        • 对于persons.Where( person => person.Name.ToUpper == “INIGO MONTOYA”);而言
        • 作为SQL的条件,获取的是过滤后的结果
  • 如何决定是转换为委托还是表达式树
    • 看需要的参数类型
    • 对于IEnumerable<T> 类型
      • Where方法声明的是接收Func<TSource, bool > 类型
    • 对于IQueryable<T> 类型
      • Where方法声明的是接收 Expression<Func<TSource, bool >> 类型

12.6.3 解析表达式树

  • 将Lambda表达式转化为表达式树
    • Expression<TDelegate>=Lambda表达式
  • 表达式树类型重写了ToString()方法,会返回Lambda表达式的字符串形式
  • 解析:嵌套遍历表达式树

相关文章

学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习...
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面...
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生...
Can’t connect to local MySQL server through socket \'/v...
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 ...
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服...