深入理解C#(第3版)-- 【C#3】第10章 扩展方法(学习笔记)
10.1 未引入扩展方法之前的状态
代码清单10-1 为流提供附加功能的一个简单的工具类
using System.IO; public static class StreamUtil { constint 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); return tempStream.ToArray(); } } }
代码清单10-2 用StreamUtil将Web响应流复制到一个文件
WebRequest request = WebRequest.Create("http://manning.com"); using (WebResponse response = request.GetResponse()) using (Stream responseStream = response.GetResponseStream()) using (FileStream output = File.Create("response.dat")) { StreamUtil.Copy(responseStream, output); }
10.2 扩展方法的语法
10.2.1 声明扩展方法
并不是任何方法都能作为扩展方法使用——它必须具有以下特征:
它必须在一个非嵌套的、非泛型的静态类中(所以必须是一个静态方法);
它至少要有一个参数;
第一个参数必须附加this关键字作为前缀;
第一个参数不能有其他任何修饰符(比如out 或ref );
第一个参数的类型不能是指针类型。
我们将第一个参数的类型称为方法的扩展类型(extended type),即指该方法扩展了该类型——在本例中我们扩展了Stream。这不是语言规范中的官方术语,但这样方便记忆。
代码清单10-3 包含扩展方法的 StreamUtil类
public static class StreamUtil { constint 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(); } } }
10.2.2 调用扩展方法
代码清单10-4 用扩展方法复制一个流
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); }
虽然扩展方法完全可以作为普通的静态方法来使用,但是移植大量代码的时候扩展方法就很有用了。
你可能已经注意到,在这些方法调用中,没有任何迹象表明我们使用的是扩展方法,而不是Stream 类的普通实例方法。
10.2.3 扩展方法是怎样被发现的
如果编译器认为一个表达式好像是要使用一个实例方法,但没有找到与这个方法调用兼容的实例方法(如不存在具有该名称的方法,或者没有重载的版本能匹配给定的实参),就会查找一个合适的扩展方法。它会检查导入的所有命名空间和当前命名空间中的所有扩展方法,并匹配那些从表达式类型到扩展类型存在着隐式转换的扩展方法。
如果存在多个适用的扩展方法,它们可应用于不同的扩展类型(使用隐式转换),那么将使用在重载的方法中应用的“更好的转换”规则(参见9.4.4 节),来选择最合适的方法。
如果存在适当的实例方法,则实例方法肯定会先于扩展方法使用。
10.2.4 在空引用上调用方法
代码清单10-5 在空引用上调用扩展方法
using System; public static class NullUtil { public static bool IsNull(this object x) { return x == null; } } public class Test { static void Main() { object y = null; Console.WriteLine(y.IsNull()); y = new object(); Console.WriteLine(y.IsNull()); } }
代码清单10-5 的输出先是True,然后是False 。如果IsNull 是一个普通的实例方法,Main的第2行就会抛出一个异常。但是,这里的null是IsNull的实参。在扩展方法问世前,y.Isnull()这样的写法虽然可读性更好,却不合法,只能采用NullUtil.IsNull(y) 这样的写法。
在框架中,有一个特别明显的例子可以证明这种写法的好处:string.IsNullOrEmpty。
10.3 .NET 3.5 中的扩展方法
10.3.1 从Enumerable 开始起步
延迟执行 构造的可枚举的实例并不会做大部分工作。它只是将东西准备好,使数据能在适当的位置以一种“just-in-time ”的方式提供。这称为延迟执行,是LINQ的一个
核心部分。
10.3.2 用Where 过滤并将方法调用链接到一起
代码清单10-8 用Lambda表达式作为Where 方法的参数,从而只保留奇数
var collection = Enumerable.Range(0, 10) .Where(x => x % 2 != 0) .Reverse(); foreach (var element in collection) { Console.WriteLine(element); }
扩展方法允许将静态方法调用链接到一起。这其实是扩展方法存在的主要原因之一。
10.3.3 插曲:似曾相识的Where 方法
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate) { if (source == null || predicate == null) { thrownew ArgumentNullException(); } return WhereImpl(source, predicate); } private static IEnumerable<T> WhereImpl<T>(IEnumerable<T> source, Func<T, bool> predicate) { foreach (T item in source) { if (predicate(item)) { yield return item; } } }
10.3.4 用Select方法和匿名类型进行投影
Enumerable中最重要的投影方法就是Select ——它操纵一个IEnumerable<TSource>,把它投影成一个IEnumerable<TResult>。具体的投影是通过一个Func<TSource, TResult>来完成的,它代表要在每个元素上执行的转换,采用的是委托的形式。
10.3.5 用OrderBy方法进行排序
对数据排序是处理数据时的一项常规要求。在LINQ中,这一般是通过OrderBy或OrderByDescending方法来实现的。如果需要根据数据的多个属性排序,那么后面还可以跟随ThenBy 或ThenByDescending 。
需要注意的是,排序不会改变原有集合——它返回的是新的序列,所产生的数据与输入序列相同,当然除了顺序。
10.3.6 涉及链接的实际例子
1. 聚合:工资汇总
company.Departments .Select(dept => new { dept.Name, Cost = dept.Employees.Sum(person => person.Salary) }) .OrderByDescending(deptWithCost => deptWithCost.Cost);
2. 分组:统计分配给开发者的bug 数量
bugs.GroupBy(bug => bug.AssignedTo) .Select(list => new { Developer = list.Key, Count = list.Count() }) .OrderByDescending(x => x.Count);
10.4 使用思路和原则
我隐藏真相是为了让你看到更大的真相。
10.4.1 “扩展世界”和使接口更丰富
http://blogs.msdn.com/b/wesdyer/
对于一个给定的问题,程序员通常习惯于构建一个解决方案,直到最终能满足需求。现在,我们可以扩展世界来迎合解决方案,而不是一直构建方案,直到最终满足需求。如果一个库没有提供你需要的,就扩展这个库来满足你的需求。
10.4.2 流畅接口
过去英国有一个电视节目叫做Catchphrase (名言)。节目内容是,参赛者将看一个屏幕,屏幕上的一段动画会演示具有一定含义的短语或俗语,参赛者通过这些猜测其真正的含义。
在框架中,流畅接口的一个很好的例子就是OrderBy和ThenBy 方法:用Lambda表达式稍加诠释,代码准确地描述了它要做的事情。
浙公网安备 33010602011771号