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表达式的字符串形式
  • 解析:嵌套遍历表达式树

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340