十二、委托和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);
- 将方法名作为委托类型的实参传递,C#2.0会自动创建委托对象
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的条件,获取的是过滤后的结果
- 可以转换为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 举报,一经查实,本站将立刻删除。