闭包 与Lambda
先看一下这个方法:
private static string FormatBytes(long bytes)
{
string[] magnitudes = new string[] { "GB", "MB", "KB", "Bytes" };
long max =(long)Math.Pow(1024, magnitudes.Length);
return string.Format("{1:##.##} {0}",
magnitudes.FirstOrDefault(
magnitude =>
bytes > (max /= 1024)) ?? "0 Bytes",
(decimal)bytes / (decimal)max).Trim();
}
这个方法可以用来计算一个网页HTML内容的长度的,比如我们使用下面这样的方式:
WebRequest webRequest =WebRequest.Create(url);
WebResponse response =webRequest.GetResponse();
Console.Write(".....");
using (StreamReader reader =new StreamReader(response.GetResponseStream()))
{
string text =reader.ReadToEnd();
Console.WriteLine(FormatBytes(text.Length));
}
用这个方法可以获取网站请求的一个Response,然后从流中读取string文本的长度,然后可以用FormatBytes方法来得到该文本的长度,本文主要研究上面那个计算文本长度并转化为文件大小的方法FormatBytes():
①magnitudes :该方法首先定义一个字符串数组,用于后面的计算。
②max :得到该数组的1024的字符串长度数的幂,在这个例子中,就是1024的4次方。
③根据该方法的一个传入参数(该例中就是获取网页HTML文本的大小)bytes,以及max这两个变量,传入扩展方法string.FirstOrDefault中,此时,FirstOrDefault方法中的Lambda表达式形成了一个闭包:有兴趣的同学可以Reflector一下。这两个变量由于闭包的形成,在编译器生成的代码中已经被一个编译器生成的类(类似于<>c_DisplayClass1之类的东西,顺便说明一下,C#的编译器会帮助我们做很多事,比如异步Async和await生成的状态机,还有迭代器,所有这些由C#编译器生成的类,都是由<>这样的尖括号前缀表明的。)的字段所取代。也就是说,本来这两个变量的作用域在方法内部,编译器为了能够在方法的栈帧销毁后继续使用这两个变量(为了Lambda表达式,更准确的说,为了Lambda表达式的延迟执行),将这两个局部(本地)变量的级别提升为类字段的级别。
接着来说string.FirstOrDefault,string类型实现了IEnumerable接口,所以能使用IEnumerable上面的扩展方法,FirstOrDefault内接收一个Func<string,bool>
的predicate(谓词),在这个方法中,该predicate为:
magnitude =>bytes > (max /= 1024))
由于Func<string,bool>
返回的是一个string,所以该扩展方法会立即执行。该扩展方法会使用这个predicate来遍历magnitudes,每遍历一次,max都要除以1024(max /= 1024),直到返回true。当然,如果找不到这个值,会返回一个默认值,该默认值根据不同类型返回不同的默认值。