未引入扩展方法之前的状态
#region 10-1为流提供附加功能的一个简单的工具类 public static class StreamUtil { 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)// 读入缓冲区中的总字节数。 如果当前可用的字节数没有请求的字节数那么多,则总字节数可能小于请求的字节数,或者如果已到达流的末尾,则为零 (0)。 { output.Write(buffer, 0, read); } } public static byte[] ReadFully(Stream input) { using (MemoryStream tempStream = new MemoryStream()) { Copy(input, tempStream);//复制流 return tempStream.ToArray();//将流的内容写入字节数组 } } } #endregion #region 10-2用StreamUtil将Web响应流复制到一个文件 WebRequest request = WebRequest.Create("http://www.baidu.com");//为指定的 URI 方案初始化新的 System.Net.WebRequest 实例。 using (WebResponse response = request.GetResponse())//当在子类中被重写时,返回对 Internet 请求的响应。 using (Stream responseStream = response.GetResponseStream())// 当在子类中重写时,从 Internet 资源返回数据流。 using (FileStream output = File.Create("response.txt"))//在指定路径中创建或覆盖文件。 { StreamUtil.Copy(responseStream, output);//StreamUtil负责循环,每次都向响应流索取更多的数据,只至接受到所有数据 } #endregion扩展方法的语法
扩展方法的使用——必须具有以下特征
- 它必须在一个非嵌套的,非泛型的静态类中(必须是一个静态方法)
- 它至少要有一个参数
- 第一个参数必须附加this关键字作为前缀
- 第一个参数不能有其他任何修饰符(out或ref)
- 第一个参数的类型不能是指针类型
我们将第一个参数类型称为方法的扩展类型,即指该方法扩展了改类型
#region 10-3包含扩展方法的StreamUtil类 public static class StreamUtil { 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); return tempStream.ToArray(); } } } #endregion调用扩展方法
#region 10-4用扩展方法复制一个流(.NET4.0一下使用扩展方法) WebRequest request = WebRequest.Create("http://www.baidu.com");//为指定的 URI 方案初始化新的 System.Net.WebRequest 实例。 using (WebResponse response = request.GetResponse())//当在子类中被重写时,返回对 Internet 请求的响应。 using (Stream responseStream = response.GetResponseStream())// 当在子类中重写时,从 Internet 资源返回数据流。 using (FileStream output = File.Create("response.txt"))//在指定路径中创建或覆盖文件。 { responseStream.CopyTo(output);//编译器将重载合适的方法,其中包括扩展方法,这里使用Stream.CopyTo,而不是使用StreamUtil。如果存在适当的实例方法,实例方法将优先于扩展方法使用,但编译器不会告知存在一个与现有实力方法匹配的扩展方法 } #endregion扩展方法是怎样被发现的
如果编译器认为一个表达式好像是要使用一个实例方法,但没有找到与这个方法调用兼容的实例方法,就会查找一个合适的扩展方法。
为了决定是否使用一个扩展方法,编译器必须能区分扩展方法与某静态类中恰好具有合适签名的其他方法,它会检查类和方法是否具有System.Runtime.CompilerServices.ExtensionAttributte。
在空引用上调用方法
#region 10-5在空引用上调用方法 public static class NullUtil { public static bool IsNull(this object x) { return x == null; } #endregion #region 10-5 object y = null; Console.WriteLine(y.IsNull());//如果IsNull是一个普通实例方法,抛出一个异常 y = new object(); Console.WriteLine(y.IsNull()); #endregion扩展方法可以和扩展类型的一个现有的静态方法具有相同的签名
.NET3.5中的扩展方法
在框架中,扩展方法最大的用途就是为LINQ服务。LINQ提供器包含了几个供辅助的扩展方法,有两个类特别醒目,Enumerable和Queryable,两者都在System.Linq命名空间中。
Enumerable扩展的是IEnumerable<T>,Queryable扩展的是IQueryable<T>
Enumerable使用
#region 10-6用Enumerable.Range打印数字0-9 var collection = Enumerable.Range(0, 10);//延迟执行 foreach (var element in collection) { Console.WriteLine(element); } #endregion #region 10-7用Reverse方法来反转一个集合 var collection = Enumerable.Range(0, 10).Reverse();//一个可枚举实例返回另一个可枚举实例的编程模式 foreach (var element in collection) { Console.WriteLine(element); } #endregion框架提供的扩展方法会尽量尝试对数据进行“流式”或者说“管道”传输。要求一个迭代器提供下一个元素时,它通常会从它链接的迭代器获取一个元素,处理那个元素,在返回符合要求的结果,而不占用自己更多的存储空间。缓冲和管道传输方式的差别就像是加载整个DataSet读取数据和用一个DataReader来每次处理一条记录的差别。
用where过滤并将方法调用链接到一起
#region 10-8用Lambda表达式作为where方法的参数,从而只保留奇数 ////如果Reverse和where不是扩展方法,代码需要如下编写 //var collection = Enumerable.Range(0, 10); //collection = collection.Where(x => x % 2 != 0); //collection = collection.Reverse(); ////Enumerable和Queryable中出现扩展方法的原因:LINQ针对数据处理进行了专门的调整,将各个单独的操作链接成一条管道,然后让信息在管道中传输 var collection = Enumerable.Range(0, 10).Where(x => x % 2 != 0).Reverse();//返回相同的引用(用于易变类型) foreach (var element in collection) { Console.WriteLine(element); } StringBuilder builder = new StringBuilder(); builder.Append("<123>"); builder = builder.Replace("<", "1").Replace(">", "2");//返回新实例(用于不易变类型) #endregion用select方法和匿名类型进行投影
#region 10-9用Reverse方法来反转一个集合 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); } #endregion用OrderBy方法进行排序
#region 10-10根据两个属性对序列进行排序 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); } #endregion