通过这种方法,我们可以将新功能“注入”到已有的程序集中,而我们并不需要拥有那个程序集的源代码,也可以用来实现在不修改原始类型声明的条件下,强迫一个类型来支持一组成员(在多态性中很有用)。
定义扩展方法的第一个限制,就是他们必须被定义在静态类中,并且每个扩展方法也必须被声明为静态的;第二个限制,所有的扩展方法的第一个参数都使用this关键字;第三个限制,无论是直接访问内存中的实例还是静态地通过静态类的定义,都可以调用任意一个扩展方法。
呃,其实我看到这里也有些晕,还是用实例来说话吧 :-)
定义扩展方法
下面是一个叫做MyExtensions的类,定义了两个扩展方法。第一个方法使用了System.Reflection命名空间,可以使得.NET基类库的所有对象有一个DisplayDefiningAssembly(),第二个方法是个翻转int型数据的函数,比如说可以把124变成421。
static class MyExtensions
{
public static void DisplayDefiningAssembly(this object obj)
{
// 该方法允许任意object显示它被定义的程序集
Console.WriteLine("{0} lives here:\n\t->{1}\n", obj.GetType().Name, Assembly.GetAssembly(obj.GetType()));
}
public static int ReverseDigits(this int i)
{
char[] digits = i.ToString().ToCharArray(); // 先转换为string,再存到数组中
Array.Reverse(digits); // 逆序排列
string newDigits = new string(digits);
return int.Parse(newDigits); // 以int类型返回
}
public static void Foo(this int i)
{
//Int32 类型拥有的Foo()方法
Console.WriteLine("{0} rings the bell.", i);
}
public static void Foo(this int i, string message)
{
//重载的 Foo() 方法
Console.WriteLine("{0} rings the bell and said: {1}", i, message);
}
}
可以看到所有方法的第一个参数具有一个this关键字。第一个方扩展的是System.Object,而第二个方法则只能扩展整型,如果一个非整型试图调用这个方法,将会得到一个编译时错误。一个扩展方法可以拥有多个参数,但只有第一个需要加this,这在后面被重载的方法里可以看到。
另外一个要注意的问题是,带有扩展方法的类不能嵌套在另一个类中,它必须是顶层类。
从实例级别调用扩展方法
好,现在有了这两个扩展方法,我们来看一看任意一个对象(当然,必须是在.NET基类库中的)是怎么执行DisplayDefiningAssembly()的,而一个System.Int32类型是如何执行ReverseDigits()和Foo()的。
static void Main(string[] args)
{
// int 类型被指定了一个新方法
int myInt = 12345678;
myInt.DisplayDefiningAssembly();
// DataSet也是
System.Data.DataSet ds = new System.Data.DataSet();
ds.DisplayDefiningAssembly();
// SoundPlayer也一样
System.Media.SoundPlayer sp = new System.Media.SoundPlayer();
sp.DisplayDefiningAssembly();
// 为 int 类型新加的功能
Console.WriteLine("Value of myInt: {0}", myInt);
Console.WriteLine("After reversed: {0}", myInt.ReverseDigits());
// 测试重载的扩展方法
myInt.Foo();
myInt.Foo("This is the sample provided by SpadeQ!");
// 下面这个就不行了
bool b = true;
// b.Foo();
Console.ReadLine();
}
将上面这个Main方法添加到上面的类中,作为程序的入口方法,然后执行,可以看到下面的结果:

静态地调用扩展方法
回想一下,所有扩展方法的第一个参数都被添加了一个this关键字,如果我们想一想在这背后发生了些什么(可以使用ildasm.exe或者Lutz Roeder's Reflector),我们将看到编译器简单地调用了normal静态方法,将调用这些方法的变量当作一个参数来传送。将上面Main方法中的语句替换如下:
static void Main(string[] args)
{
int myInt = 12345678;
MyExtensions.DisplayDefiningAssembly(myInt);
System.Data.DataSet ds = new System.Data.DataSet();
MyExtensions.DisplayDefiningAssembly(ds);
System.Media.SoundPlayer sp = new System.Media.SoundPlayer();
MyExtensions.DisplayDefiningAssembly(sp);
Console.WriteLine("Value of myInt: {0}", myInt);
Console.WriteLine("After reversed: {0}", MyExtensions.ReverseDigits(myInt));
MyExtensions.Foo(myInt);
MyExtensions.Foo(myInt, "This is the sample provided by SpadeQ!");
Console.ReadLine();
}
运行结果仍然是一样的。也就是说,从某个对象调用扩展方法,看起来像是实例级别的调用,其实是编译器在忽悠我们而已~~~
生成以及使用扩展库
最后一个有关扩展方法的话题就是建立扩展库。一切有用的东西都可以封装到库里以供复用,这是现代编程的王道。为了演示这个过程,重新建立一个.NET Library工程,然后将代码转移进去,看起来应该如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace MyExtensionLibrary
{
public static class MyExtensions
{
public static void DisplayDefiningAssembly(this object obj)
{
// 该方法允许任意object显示它被定义的程序集
Console.WriteLine("{0} lives here:\n\t->{1}\n", obj.GetType().Name, Assembly.GetAssembly(obj.GetType()));
}
public static int ReverseDigits(this int i)
{
char[] digits = i.ToString().ToCharArray(); // 先转换为string,再存到数组中
Array.Reverse(digits); // 逆序排列
string newDigits = new string(digits);
return int.Parse(newDigits); // 以int类型返回
}
}
}
注意,如果想在库外调用这些方法,必须在类名前面加上public,因为C#默认的访问级别是private。在这里,还可以显示指明扩展应用,即在public static ...前面加上一个[Extension],当然现在并不必须这么做。
编译完成之后回到我们原来的那个工程,添加对这个新编译出来的MyExtensionLibrary.dll的引用,修改代码如下:
using System;
using MyExtensionLibrary;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int myInt = 12345678;
myInt.DisplayDefiningAssembly();
System.Data.DataSet ds = new System.Data.DataSet();
ds.DisplayDefiningAssembly();
System.Media.SoundPlayer sp = new System.Media.SoundPlayer();
sp.DisplayDefiningAssembly();
Console.WriteLine("Value of myInt: {0}", myInt);
Console.WriteLine("After reversed: {0}", myInt.ReverseDigits());
myInt.Foo();
myInt.Foo("This sample is provided by SpadeQ");
Console.ReadLine();
}
}
}
可以得到同样的结果,就像使用普通的方法一样。所有object类型都已经被加上了DisplayDefiningAssembly()扩展,所有的int类型还被加上了ReverseDigits()扩展,前提是我们无需分拆出object和int的代码,我们甚至连它的内部结构都不知道,但仍然可以向它们的数据类型“注入”我们自己想要的函数。
0 评论:
发表评论