C#扩展方法调用简析

通过前面两节的学习,我们了解到:扩展方法是一种特殊的静态方法,扩展方法的定义方法和一般的静态方法的定义方法类似,唯一的区别是在第一个参数的前面要加上关键字this作为修饰符。

扩展方法的调用方式和扩展类型上的实例方法的调用方式一样。既然扩展方法可以像扩展类型上的实例方法一样进行调用,那么编译器怎样决定是否要使用一个扩展方法呢。对于这个问题,编译器是按照下面的流程来工作的:当编译器发现一个表达式好像是使用一个实例方法,但是在导入的所有命名空间和当前的命名空间中又没有找到与这个实例方法调用兼容的实例方法,就会在导入的所有命名空间和当前的命名空间中查找一个合适的扩展方法。在这里,我们记住两点就可以了:(1)实例方法先于扩展方法被使用;(2)查找扩展方法的范围是导入的所有命名空间和当前的命名空间,这个很好理解。

其实,还有一个问题需要阐述一下,当查找结果中存在多个适用的扩展方法时,编译器是怎样取舍的呢。其实编译器只需在重载的方法中寻找最合适的一个即可。

 

下面来看个简单的例子,希望通过这个例子能让大家对扩展函数的调用有更深的了解。

第一步,在命名空间ConsoleApplication2下创建扩展方法Print(),使用该扩展方法可以完成简单的打印操作,该扩展方法位于静态类ExtendFunction中。

[csharp][/csharp] view plaincopy

  1. namespace ConsoleApplication2
  2. {
  3.     public static class ExtendFunction
  4.     {
  5.         public static void  Print(this object o)
  6.         {
  7.             Console.WriteLine(o);
  8.         }
  9.     }
  10. }

第二步,在命名空间ConsoleApplication2下创建类TestClass,利用该类中的Print()方法同样可以完成打印操作。

[csharp][/csharp] view plaincopy

  1. namespace ConsoleApplication2
  2. {
  3.     public class TestClass
  4.     {
  5.         private object priStr;
  6.         public TestClass(object o)
  7.         {
  8.             this.priStr = o;
  9.         }
  10.         public void Print()
  11.         {
  12.             Console.WriteLine(this.priStr);
  13.         }
  14.     }
  15. }

第三步,在Main函数实现以下函数调用的代码:

[csharp][/csharp] view plaincopy

  1. namespace ConsoleApplication2
  2. {
  3.     class Program
  4.     {
  5.         static void Main(string[] args)
  6.         {
  7.             string s = “hell,world!”;
  8.             int i = 32;
  9.             TestClass tc1 = new TestClass(s);
  10.             TestClass tc2 = new TestClass(i);
  11.             //实例方法调用
  12.               tc1.Print();
  13.             //静态方法调用
  14.               s.Print();
  15.             //实例方法调用
  16.               tc2.Print();
  17.             //静态方法调用
  18.               i.Print();
  19.         }
  20.     }
  21. }

第四步,执行程序,得到以下结果:

其实对于这个结果,我们应该早就预料到了。因为该实例中的扩展方法Print()与TestClass类提供的Print()方法从功能上来说是完全等价的。

通过这个例子,让我们进一步了解到,扩展方法的调用方式和扩展类型上的实例方法的调用方式完全一样,如本例中的“tc1.Print()”及“s.Print()”。

更重要的是,通过在方法前设置断点加单步调试来跟踪代码流走方向,让我们进一步认识到:实例方法确实是先于扩展方法被使用的。那我又是怎样确定这点的呢?现在我就将我的做法娓娓道来:(1)首先,在扩展方法Print()及TestClass类的成员方法Print()前设置断点;(2)接下来,启动单步调试,跟踪代码流走方向,注意到:函数调用“tc1.Print()”及“tc2.Print()”调用的方法是TestClass类的实例方法Print();而函数调用“s.Print()”及“i.Print()”调用的是扩展方法Print();(3)紧接着,将TestClass类的成员方法Print()注释,再次启动单步调试跟踪代码流走方向,发现:此次函数调用“tc1.Print()”,“tc2.Print()”,“s.Print()”,“i.Print()”均调用的是扩展方法Print(),完全是按照“先实例方法再扩展方法”的调用流程来完成函数调用。当然,此次程序的运行结果与前次也有一定的差别,具体结果见下图:

为什么会这样呢?原因很简单,你说是吗?好好想想吧。

最后,在静态类ExtendFunction中添加以下代码:

[csharp][/csharp] view plaincopy

  1. public static void Print(this object o, bool isPrint)
  2. {
  3.     if (isPrint == true)
  4.     {
  5.         Console.WriteLine(o);
  6.     }
  7. }

再次启动单步调试,发现函数调用“s.Print()”及“i.Print()”调用的是扩展方法是具有“public static void  Print(this object o)”这个函数前面的扩展方法,而不是具有“public static void Print(this object o, bool isPrint)”这个函数前面的扩展方法。看来存在多个适用的(仅方法名称相同)扩展方法时,编译器确实是在重载的方法中寻找最合适的一个来执行的。

标签