声明扩展方法
我们并不能将任何的方法都作为扩展方法——扩展方法必须要有以下的一些特性:
- 它必须是被包含在一个非嵌套的, 非泛型的静态类当中(因此必须是静态方法)
- 必须至少拥有一个参数
- 第一个参数必须是由this关键字开头
- 第一个参数不能有其他的修饰符, 例如out / ref
- 第一个参数类型不能是一个指针类型
扩展方法本身可以是泛型的, 可以有返回值, 除了第一个参数外, 也可以包含ref / out参数, 可以作为partial类的一部分, 可以在迭代块当中来实现, 可以使用nullable类型——任何语法, 只要遵循了上述的约束.
我们把方法的第一个参数类型叫做extended type(被扩展的类型). 这并不是一个官方术语, 不过是一个有用的简短表达方式.
上面的列表不仅仅提供了所有的限制, 同时也表明了将静态方法变为扩展方法你需要做的步骤——加上this关键字. 以下的代码将我们之前的例子改造为扩展方法:
1: public static class StreamUtil
2: {
3: const int BufferSize = 8192;
4: public static void CopyTo(this Stream input,
5: Stream output)
6: {
7: byte[] buffer = new byte[BufferSize];
8: int read;
9: while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
10: {
11: output.Write(buffer, 0, read);
12: }
13: }
14: public static byte[] ReadFully(this Stream input)
15: {
16: using (MemoryStream tempStream = new MemoryStream())
17: {
18: CopyTo(input, tempStream);
19: return tempStream.ToArray();
20: }
21: }
22: }
相比之前的例子, 我紧紧增加了两个this修饰符, 同时将方法名称从Copy改为CopyTo. 虽然现在在ReadFully当中看起来有点奇怪, 不过之后我们会看到这个改动会让我们的调用代码看起来更加自然.
调用扩展方法
简单来说, 改造实例代码来使用StreamUtil和改造该工具类一样简单. 这次, 我们不是要增加什么代码, 相反, 我们要去掉一些代码. 下面的代码使用我们提过的新的”语法”来调用CopyTo. 我说”新”实际上它根本就不新——因为它和我们一直使用的调用实例方法的语法一模一样.
1: WebRequest request = WebRequest.Create("http://manning.com");
2: using (WebResponse response = request.GetResponse())
3: using (Stream responseStream = response.GetResponseStream())
4: using (FileStream output = File.Create("response.dat"))
5: {
6: responseStream.CopyTo(output);
7: }
在上面的代码中, 至少它看起来像是我们让reponse流来完成copying的操作. 实际上幕后依然是StreamUtil在完成实际的工作, 但代码读起来更加自然了. 编译器在背后实际上仅仅是将CopyTo的调用转到了对StreamUtil.CopyTo的调用, 并传递responseStream作为方法的第一个参数.
你可能会注意并没有什么特别的东西指示我们到底是正在调用Stream的扩展方法还是常规的实例方法. 如果使用VS2008, 当你把鼠标移到一个方法调用上面的时候, 如果它是一个扩展方法, 那么将会出现一个明显的提示. 另外智能提示也会帮你指出哪些方法是扩展方法, 一个向下的箭头图标会出现在扩展方法前. 当然我们并不需要仔细到观察每一个方法来了解它是不是扩展方法, 因为多数情况下, 我们并不关心我们正在调用的到底是一个扩展方法还是一个实例方法.
在我们调用代码中, 还有一件可能会让人感到奇怪的事情, 那就是——StreamUtil根本就没有不提到! 编译器是怎么知道我们是要使用扩展方法的呢? 待续!