C# in Depth (第十章 扩展方法)
10.1 未引入扩展方法之前的状态
- 想为一个类型添加一些成员
- 你不需要为类型的实例添加任何更多的数据
- 你不能改变类型本身,因为是别人的代码
- 想处理的是接口不是类
为流提供简单功能的一个简单工具类
public static class StreamUtilNoExtensions { const int BufferSize = 8192; public static void Copy(Stream input, Stream output) { byte[] buffer = new byte[BufferSize]; int read; while ((read = input.Read(buffer, 0, buffer.Length)) > 0) { output.Write(buffer, 0, read); } } public static byte[] ReadFully(Stream input) { using (MemoryStream tempStream = new MemoryStream()) { Copy(input, tempStream); if (tempStream.Length == tempStream.GetBuffer().Length) { return tempStream.GetBuffer(); } return tempStream.ToArray(); } } }
用StreamUtil将Web响应流复制到一个文件
class UsingStreamUtilWithoutExtensions { static void Main() { WebRequest request = WebRequest.Create("http://manning.com"); using (WebResponse response = request.GetResponse()) using (Stream responseStream = response.GetResponseStream()) using (FileStream output = File.Create("response.dat")) { StreamUtilNoExtensions.Copy(responseStream, output); } } }
10.2 扩展方法的语法
10.2.1声明扩展方法
- 它必须在一个非嵌套的,非泛型的静态类中(所以必须是一个静态方法)
- 它至少要有一个参数
- 第一个参数必须附加this关键字作为前缀
- 第一个参数不能有其他任何修饰(比如out, ref)
- 第一个参数的类型不能是指针类型。
包含扩展方法的StreamUtil类
public static class StreamUtilWithExtensions { const int BufferSize = 8192; public static void CopyTo(this Stream input, Stream output) { byte[] buffer = new byte[BufferSize]; int read; while ((read = input.Read(buffer, 0, buffer.Length)) > 0) { output.Write(buffer, 0, read); } } public static byte[] ReadFully(this Stream input) { using (MemoryStream tempStream = new MemoryStream()) { CopyTo(input, tempStream); if (tempStream.Length == tempStream.GetBuffer().Length) { return tempStream.GetBuffer(); } return tempStream.ToArray(); } } }
10.2.2调用扩展方法
用扩展方法复制一个流
class UsingStreamUtilWithExtensions { static void Main() { WebRequest request = WebRequest.Create("http://manning.com"); using (WebResponse response = request.GetResponse()) using (Stream responseStream = response.GetResponseStream()) using (FileStream output = File.Create("response.dat")) { responseStream.CopyTo(output); } } }
10.2.3扩展方法是怎样被发现的
编译器根据using找到扩展方法。
10.2.4在空引用上调用方法
static class NullUtil { public static bool IsNull(this object x) { return x == null; } }
10.3.1 从Enumerable开始起步
用Enumerable.Range打印数字0~9
class RangeEnumeration { static void Main() { var collection = Enumerable.Range(0, 10); foreach (var element in collection) { Console.WriteLine(element); } } }
- 延迟执行 Range方法并不会真的构造含有适当数字的列表,它只是在恰当的时间生成那些数。换言之,构造的可枚举的实例并不会做大部分工作。它只是将东西准备好,使数据能够在适当的位置以一种 just-in-time的方式提供。这称为延迟执行,是LINQ的一个核心部分。
用Reverse方法来反转一个集合
class RangeReversal { static void Main() { var collection = Enumerable.Range(0, 10) .Reverse(); foreach (var element in collection) { Console.WriteLine(element); } } }
- 效率问题:缓冲和流式技术。
- 框架提供的扩展方法会尽量尝试对数据进行“流式”(stream)或者"管道"(pipe)传输。要求一个迭代器提供下一个元素时,它通常会从它链接的迭代器获取一个元素,处理那个元素。再返回符合要求的结果,而不用占用自己更多的存储空间。执行简单的转换和过滤操作时,这样做非常简单,可用的数据处理起来也非常高效。
- 但是,对于某些操作来说比如反转或排序,就要求所有数据都处于可用状态,所以需要加载所有数据到内存来执行处理。
- 缓冲和管道传输方式,这两者的差别很像是加载整个DataSet读取数据和用一个DataSet读取数据和用一个DataReader来每次处理一条记录的差别。
- 流式传输(streaming)也叫惰性求值(lazy evaluation),缓冲传输(bufferring)也叫热情求值(eager evaluation)。
10.3.2 用Where过滤并将方法调用链接到一起
用Lambda表达式作为where方法的参数,从而只保留奇数
class RangeFiltering { static void Main() { var collection = Enumerable.Range(0, 10) .Where(x => x % 2 != 0) .Reverse(); foreach (var element in collection) { Console.WriteLine(element); } } }
- “返回相同的引用”模式用于易变类型,而“返回新实例(该实例为原始实例更改后的副本)“模式则用于不易变类型。
10.3.3 插曲:似曾相识的where方法
10.3.4 用Select方法和匿名类型进行投影
class RangeProjection { static void Main() { var collection = Enumerable.Range(0, 10) .Where(x => x % 2 != 0) .Reverse() .Select(x => new { Original = x, SquareRoot = Math.Sqrt(x) }); foreach (var element in collection) { Console.WriteLine("sqrt({0})={1}", element.Original, element.SquareRoot); } } }
10.3.5 用OrderBy方法进行排序
class RangeOrdering { static void Main() { var collection = Enumerable.Range(-5, 11) .Select(x => new { Original = x, Square = x * x }) .OrderBy(x => x.Square) .ThenBy(x => x.Original); foreach (var element in collection) { Console.WriteLine(element); } } }