【笔记】【LINQ编程技术内幕】第五章 理解Lambda表达式和闭包

了解由函数指针到Lambda表达式得演化过程

C++函数指针 ,其实这个例子使用的是C++\CLI

#include "stdafx.h"

using namespace System;

typedef void (*FunctionPointer)(System::String ^str);

void HelloWorld(System::String ^str)
{
    Console::WriteLine(str);
	Console::ReadLine();
}

int main(array<System::String ^> ^args)
{
	FunctionPointer fp = HelloWorld;
	fp(L"Hello World");
	return 0;
}

C# 函数指针

delegate void FunctionPointer(String str);

static void Main(string[] args)
{
	FunctionPointer fp = HelloWorld;
	fp("Hello World!");
}

static void HelloWorld(string str)
{
	Console.WriteLine(str);
	Console.ReadLine();
}

匿名委托

delegate void FunctionPointer(string str);

static void Main(string[] args)
{
	FunctionPointer fp = delegate (string s)
	{
		Console.WriteLine(s);
		Console.ReadLine();
	};

	fp("Hello World!");
}

Lambda表达式

delegate void FunctionPointer(string str);

static void Main(string[] args)
{
	FunctionPointer fp = s => Console.WriteLine(s);

	fp("Hello World!");
	Console.ReadLine();
}

使用.NET 框架自带得泛型委托

Action<double> print = amount => Console.WriteLine("{0:c}", amount);
Action<double> michiganSalesTax = amount => print(amount *= 1.06);

var amounts = new double[]{10.36, 12.00, 134};

Array.ForEach<double>(amounts, michiganSalesTax);

编写基本的Lambda表达式

System命名空间中定义了泛型委托Action、Func以及Predicate,Lambda表达式可以赋值给这些类型的实例或匿名类型。

  • Action 用于在泛型参数上执行一个操作
  • Func 用于在参数上执行一个操作,并返回一个值
  • Predicate 用于定义一组条件却确定参数是否复核这些条件

自动属性

class Program
{
	static void Main(string[] args)
	{
		IronChef Batali = new IronChef
		{
			Name = "Mariio Batali",
			Specialty = "Italian"
		};
		Console.WriteLine(Batali.Name);
	}
}

public class IronChef
{
	public string Name { get; set; }
	public string Specialty { get; set; }
}

阅读Lambda表达式

Lambda表达式由一个左部、=>、以及一个右部组成,如(x,y) => x + y,左部表示输入参数,又不表示需要求职的表达式。

Func<int, int, int> add = (int x, int y) => x + y;
Console.WriteLine(add(3, 4));

Lambda表达式用作泛型活动

Action<string, TextWriter> print = (s, writer) => writer.WriteLine(s);

print("Console", Console.Out);
StreamWriter stream = File.AppendText("c:\\temp\\text.txt");
stream.AutoFlush = true;
print("File", stream);

搜索字符串

var recipes = new[]{
	"Crepes Florentine", 
	"Shrimp Che Paul",
	"Beef Burgundy", 
	"Rack of Lamb",
	"Tacos", 
	"Rosemary Raosted Chicken",
	"Juevos Rancheros", 
	"Buffalo Chicken Nachos"
};

var results = recipes.Where( s => s.Contains("Chicken"));

Array.ForEach<string>(results.ToArray<string>(),
  s => Console.WriteLine(s));

获取斐波那契数列中的奇数

List<int> fiboList = new List<int>();
fiboList.AddRange(new[] { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 });

Predicate<int> match = n => n % 2 == 1;
var odds = fiboList.FindAll(match);

ObjectDumper.Write(odds);

Lambda表达式用作泛型谓词

泛型委托Predicate接受一个由T指定的参数并返回一个布尔值。Predicate用在诸如Array.Find和Array.FindAll这样的函数中。Predicate可以通过普通函数、匿名函数或Lambda表达式来初始化。

Rectangle[] rects = new Rectangle[100];

private void Form1_Load(object sender, EventArgs e)
{
	Random random = new Random(DateTime.Now.Millisecond);
	int x, y, width, height;

	for (int i = 0; i < 100; i++)
	{
		x = random.Next(this.ClientRectangle.Width / 2);
		y = random.Next(this.ClientRectangle.Height / 2);
		width = random.Next(200);
		height = random.Next(200);
		rects[i] = new Rectangle(x, y, width, height);
	}
}

private void Form1_Paint(object sender, PaintEventArgs e)
{
	Random random = new Random(DateTime.Now.Millisecond);
	Predicate<Rectangle> area = rect => (rect.Width * rect.Height) <= (random.Next(200) * random.Next(200));

	Rectangle[] matches = Array.FindAll(rects, area);
	...
}

将Lambda表达式绑定到控件事件

public Form1()
{
	InitializeComponent();
	button1.Click += (s, e) => MessageBox.Show("Click!");
}

利用Lambda表达式进行动态编程

LINQ时间上是基于扩展方法和Lambda表达式的语法糖

使用Select和Lambda表达式

var numbers = new int[] { 1, 2, 3, 4, 5, 6 };
foreach (var result in numbers.Select(n => n))
	Console.WriteLine(result);

利用复合类型初始化投影出一个新类型

var numbers = new int[] { 1, 2, 3, 4, 5, 6 };
foreach (var result in numbers.Select(n => new { Number = n, IsEven = n % 2 == 0 }))
	Console.WriteLine(result);

使用Where和Lambda表达式

var words = new string[] { "Drop", "Dead", "Fred" };
IEnumerable<string> hasDAndE = words.Where(s => s.Contains('D') && s.Contains('e'));

foreach (string s in hasDAndE)
	Console.WriteLine(s);

使用OrderBy和Lambda表达式

var numbers = new int[]{1, 3, 5, 7, 9, 2, 4, 6, 8, 10 };
IEnumerable<int> ordered = numbers.OrderBy(n => n);
foreach (var num in ordered)
	Console.WriteLine(num);

将Lambda表达式编译为代码或数据

Lambda表达式既可以编译为代码,也可以编译为数据。当Lambda表达式赋值给一个变量、字段或委托时,编译器将发出可执行IL。比如,num => num % 2 == 0所发出的IL跟接收一个整型值,执行除法,然后将余数跟0做比较的函数所发出的IL是一样的。

当Lambda表达式被赋值给一个类型为System.Linq.Expressions.Expression<TDelegate>的变量、字段或参数时,编译器发出的代码将时一个表达式树
表达式树是Lambda表达式在内存中的表示。这些表达式是能够被编译的,而且他们所基于的Lambda表达式跟别的Lambda表达式一样都能够被调用。不过,更重要的是,Lambda表达式是可以被传输的,而且能够通过引用程序编程接口转换成新的形式。

static void Main(string[] args)
{
	Test();
	Test2();
}

static void Test2()
{
	Expression<Func<int, bool>> exp = num => num % 2 == 0;
	
	Console.WriteLine("Body: {0}", exp.Body.ToString());
	Console.WriteLine("Node Type: {0}", exp.NodeType);
	Console.WriteLine("Type: {0}", exp.Type);
	
	Func<int, bool> lambda = exp.Compile();
	
	Console.WriteLine(lambda(2));
}

static void Test()
{
	Func<int, bool> lambda = num => num % 2 == 0;
}

Lambda表达式和闭包

如果将一个变量声明在一个函数内部,该变量就只会存在于声明范围内的栈内存空间中。因此,在函数返回之后,这个本地变量也就同时被从该函数的栈空间中清除掉了。也就是说,当你在一个Lambda表达式中使用了本地变量的话,该本地变量将会在函数的栈空间清理的时候被移除。为了防止这样的问题发生,当一个依赖于本地变量的Lambda表达式需要从函数中返回出去,编译器就会创建一个闭包。

闭包是一个自动生成的类,它含有一个Lambda表达式,并为每个被调用到的本地变量生成一个字段。这些本地变量的值会被赋值到相应的字段中,一边拓展这些本地变量的生命周期。

static void Main(string[] args)
{
	UsesClosure();
}

static void UsesClosure()
{
	string toFind = "ed";
	var words = new string[]{"ended", "friend", "closed", "potato"};

	// Lambda表达式会被编译为一个函数。如果没有闭包,在生成的函数中是
	// 无法直接使用局部变量 toFind
	
	// 闭包会创建一个新的类,该类包含一个与局部变量 toFind包含相同值得字段
	// 以延续局部变量得生命周期
	var matches = words.Select(s => s.Contains(toFind));

	foreach (var str in matches)
		Console.WriteLine(str);
}

问题:
如果在闭包中改变局部变量得值,原局部变量得值是否改变

一、值类型传递

static void Main(string[] args)
{
	test();
}

static void test()
{
	var a = 1;
	var num = new int[] { 1, 2, 3, 4, 5 };
	var num2 = num.Select(n => new {
		A = a++,
		Num = n + a
	});


	foreach (var item in num2)
		Console.WriteLine("{0}, {1}", item.A, item.Num);
	Console.WriteLine(a);
}

结果
1, 3
2, 5
3, 7
4, 9
5, 11
6
值类型得闭包,原局部变量得值会被更改

一、引用类型传递

static void Main(string[] args)
{
	UsesClosure();
}

static void UsesClosure()
{
	string toFind = "ed";
	var words = new string[]{"endeded", "friended", "closeded", "potatoed"};
	var matches = words.Select(s => new 
	{
		Local = toFind += "ed", 
		Contained=s.Contains(toFind)
	});

	foreach (var item in matches)
		Console.WriteLine("{0}, {1}", item.Local, item.Contained);
		
	Console.WriteLine("=================================");
	Console.WriteLine(toFind);
}

结果
eded, True
ededed, False
edededed, False
ededededed, False
=================================
ededededed
引用类型得闭包,会修改原局部变量得值

热门文章

暂无图片
编程学习 ·

springboot+idea+bootstrap的带有图片的表格编辑操作

前面已经写了 批量导入,图片显示,现在写的是批量修改,后面会写用echarts+springboot 做折线图,有时间贴上 1、jsp代码如下,编辑按钮formatter: function (value, row, index) {var edit = <input class="btn btn-primary" type="button" value=&qu…
暂无图片
编程学习 ·

element dom 事件注册 on off once

/* istanbul ignore next */ // 匿名函数自执行,兼容IE-attachEvent,chrome-addEventListener export const on = (function() {if (!isServer && document.addEventListener) {return function(element, event, handler) {if (element && event && h…
暂无图片
编程学习 ·

selenium 点击按钮,打开新标签页后,无法定位新标签页的元素

使用selenium爬取页面时,在弹出浏览器界面上我们明明看到已经自动到达新标签页,却无法定位新标签页的元素原因:实际上程序并没有随浏览器上所看的标签页改变而该改变,也就是说我们在浏览器上看到已经到达标签2,实际上程序默认的还是标签1解决方案:browser.switch_to_wind…
暂无图片
编程学习 ·

一线互联网大厂300多道Java面试题【全面解析】,助你备战“金九银十”、进军BAT、斩获offer必备的核心知识点

前言今年因为疫情原因,很多人在家里宅了很长一段时间,“金三银四”黄金季也随之而然的“泡汤”,所有的跳槽涨薪的黄金季都集中在了“金九银十”季,所以程序员的竞争会对比往年更加激烈,为了备战“金九银十”需要有充足的时间复习筹备,为面试做足准备。我这里这筹备了一份…
暂无图片
编程学习 ·

动态任务

1.任务句柄 /* LED任务句柄 */ static TaskHandle_t LED_Task_Handle; 2.任务创建函数 BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, //任务函数const char * const pcName, //任务名称const uint16_t usStackDepth, //堆栈大小void * const pvParamet…
暂无图片
编程学习 ·

2016 年实验班选拔试题

SUM(10 分) 题目描述:求出在 1 到 N 之间的所有整数的总和。 输入格式:输入包含多组测试数据。每行是一组测试数据,该数据是一个绝对值不 大于 10000 的整数 N。N=0 时,表示输入结束。 输出格式:对于每组测试数据,输出一行,改行包含一个整数,是所有在 1 到 N 之 间的…
暂无图片
编程学习 ·

设计模式学习——单例模式

一、单例模式的概念1.1 概念单例模式是指 确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式的特点是隐藏其所有的构造方法。属于创建型模式。1.2 单例模式的适用场景确保任何情况下都绝对只有一个实例。例如ServletContext、ServletConfig、Applicat…
暂无图片
编程学习 ·

51单片机8*8点阵显示“中国”

#include <reg52.h> #include <intrins.h> //位移函数 sbit DIO=P3^4; //2片74HC595数据输入端 sbit S_CLK=P3^5;//串行输入时钟 sbit R_CLK=P3^6;//并行输出时钟 unsigned char code table[2][8]={0xEF,0xEF,0xEF,0x01,0x6D,0x01,0xEF,0xEF,0x01,0x7D,0x01,0x69,0…
暂无图片
编程学习 ·

jdk源码解析二之HashMap

这里写自定义目录标题HashMapputremovereplaceget扩容resize迭代器总结什么时候采用红黑树?为什么每次扩容后,是2的幂次方?为什么扩容后,相同的在原位置保存,而不同的则当前索引+之前原位置索引保存?为啥用尾插法?为什么线程不安全? HashMap HashMap的loadFactor为什么是0…
暂无图片
编程学习 ·

.NET中解决ajax跨域问题

一行代码解决:HttpContext.Response.AppendHeader(“Access-Control-Allow-Origin”,"*"); 然后,该怎么返回数据怎么返回数据
暂无图片
编程学习 ·

堪称零瑕疵!仅用了330页直接封神,我要吹爆这份RocketMQ笔记

RocketMQ天生为金融互联网领域而生,追求高可靠、高可用、高并发、低延迟 RocketMQ在阿里集团也被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景 其主要功能有:灵活可扩展性、 海量消息堆积能力、 能够保证严格的消息顺序 提供丰富的消息拉…
暂无图片
编程学习 ·

DataGrip链接mysql 数据库遇到的问题和解决方式

刚开始链接mysql 的时候没有连接成功,报错如上图所示,解决过程如下1、查看mysql 有没有起来2、如果没有起来,就3、起来之后,再次输入docker ps4、如果不行要重启虚拟机5、虚拟机重启后启动DOCKER6、启动MySQL7、
暂无图片
编程学习 ·

CUDA学习(四):cudaMalloc、cudaMemcpy和cudaFree函数

文章目录一、cudaMalloc、cudaMemcpy和cudaFree 介绍二、第一个例子,实现GPU端的加法 可以像调用C函数那样将参数传递给核函数 当设备执行任何有用的操作时,都需要分配内存,例如将计算机返回给主机。 一、cudaMalloc、cudaMemcpy和cudaFree 介绍 内存空间开辟、内存复制和内…
暂无图片
编程学习 ·

【解惑】到底是“时间片“?还是“分时轮询“?

1、任务调度任务调度对于电子类或者自动化类专业小伙伴最早接触一般都是在接触RTOS后了,然而对于计算机相关专业的小伙伴应该在学《计算机操作系统》老师对这一块讲解的非常清楚了,包括一些性能指标的定义与计算等等,不过作者这里仅仅只针对RTOS进行讲解,大家感兴趣可以找一…
暂无图片
编程学习 ·

浙江工业大学计算机技术专业考研经验分享帖

浙江工业大学复试经验分享自我介绍初试复习复试流程复试感想心态分享 自我介绍我,本科双非二本,软件工程专业,大学四年学业成绩在专业前十,拿过奖学金,参加过一些没啥含金量的比赛,不爱且不怎么会敲代码。就这样一个平凡的我决定考研了,考虑到地理,专业等因素,我将浙江…
暂无图片
编程学习 ·

国家卫生健康委办公厅关于启用全国统一电子无偿献血证的通知

国卫办医函〔2020〕447号各省、自治区、直辖市及新疆生产建设兵团卫生健康委:为落实国务院关于加快推进“互联网+政务服务”工作要求,提升无偿献血管理能力和服务质量,为无偿献血者提供便利,我委在前期试点工作基础上,依托全国血液管理信息系统,开发了全国统一的电子《无…
暂无图片
编程学习 ·

深度学习:什么是backbone,benchmark,baseline

backbone:骨干网络,比如alexnet,ZFnet,VGG,googlenet...benchmark:性能指标,比如accuracy,内存消耗,模型复杂度。.baseline:对照组,也就是被用来对比的模型。比如resnet中用来对比的CNN就是baseline。
暂无图片
编程学习 ·

【Web】Maven搭建前后端连接实例

【Web】Maven搭建前后端连接实例前言建立Maven项目项目目录正式开始配置pom.xml配置db.properties文件applicationContext.xmlspring-mvc.xmlmapper下的UserMapper.xmlbean下的User.javadao层UserDaoservice层UserServiceImpl下的UserServiceImplcontroller层UserControllerWEB…