你的位置:首页 > ASP.net教程

[ASP.net教程]使类的扩展更简单——扩展方法


1、什么是扩展方法?

    扩展方法,首先是一种方法,它可以用来扩展已定义类型中的方法成员。

    在扩展方法诞生之前,如果想为一个已有类型自定义含有特殊逻辑的新方法时,你必须重新定义一个类型来继承已有类型,以这种方式来添加方法。如果基类有抽象方法,则还要重新去实现这个抽象方法。

    这样,为了扩展一个方法,需要承担更多的因继承而产生的开销。使用继承来扩展现有类型总有点大材小用的感觉,并且值类型或密封类(不能被继承的类)等也不能被继承,不能由此获得扩展。

    于是,C#3.0提出了扩展方法。

 

2、扩展方法的使用

    2.1 定义扩展方法

 1 public static class ListExtern 2   { 3     public static int JSum(this IEnumerable<int> source) 4     { 5       if (source == null) 6       { 7         throw new ArgumentException("输入数组为空"); 8       } 9       int jsum = 0;10       bool flag = false;11 12       foreach (var i in source)13       {14         if (!flag)15         {16           jsum += i;17           flag = true;18         }19         else20         {21           flag = false;22         }23       }24       return jsum;25     }26   }

    在以上代码中,JSum方法就是一个扩展方法,它的功能是计算数组中小标为奇数的数组成员之和。并不是所有的方法都可以用作扩展方法。下列是符合扩展方法的定义规则:

(1)扩展方法必须在一个非嵌套、非泛型的静态类中定义;

(2)它至少要有一个参数;

(3)第一个参数必须加上this关键字作为前缀(第一个参数类型也称为扩展类型,即指方法对这个类型进行扩展);

(4)第一个参数不能使用任何其他的修饰符(如不能使用ref、out等修饰符);

(5)第一个参数的类型不能是指针类型。

    这些规则都是硬性规定,无论方法违反了哪一条,编译器都可能会报错,或认为它不是一个扩展方法。

     

    2.2 调用扩展方法

          成功定义了一个扩展方法后,接下来就该去调用它。

         

1 static void Main(string[] args)2     {3       List<int> source=new List<int>() {1,2,3,4,5,6,3};4       int jsum = source.JSum();5       Console.WriteLine("数组的奇数和为:"+jsum);6       Console.ReadKey();7     }

    成功调用,说明了扩展方法调用的独特性,即这里可以直接通过List<int>类型来调用扩展方法。

 

3、编译器如何发现扩展方法

     对于C# 3.0编译器而言,当它看到某个类型的变量在调用方法时,它会首先去该对象的实例方法中进行查找,如果没有找到与调用方法同名并参数一致的实例方法,编译器就回去查找存在合适的扩展方法。

     编译器会检查所有导入的命名控件和当前命名控件中的扩展方法,并将变量类型匹配到扩展类型,这里存在一个隐式转换的扩展方法。如在前面代码中,从List<T>到我们扩展的类型IEnumerable<int>就存在一个隐式转换。

     从编译器发现扩展方法的过程来看,方法调用的优先级顺序应为:类型实例方法-当前命名空间下的扩展方法-导入命名控件的扩展方法。下面就用代码来演示一下编译器发现方法的过程:

     

 1 namespace 扩展方法2 2 { 3   using 扩展方法3; 4   class Program 5   { 6     static void Main(string[] args) 7     { 8       Person p = new Person() {Name = "哈哈"}; 9       p.Print();10       p.Print("Hello");11     }12   }13 14   public class Person15   {16     public string Name { get; set; }17   }18 19   public static class Extensionclass20   {21     public static void Print(this Person per)22     {23       Console.WriteLine($"调用的是当前命名空间下的扩展方法输出,姓名为:{per.Name}");24     }25   }26 }27 28 namespace 扩展方法329 {30   using 扩展方法2;31 32   public static class CustomExtensionClass33   {34     public static void Print(this Person per)35     {36       Console.WriteLine($"调用的是CustomNamaspace命名空间下的扩展方法暑促:姓名为:{per.Name}");37     }38 39     public static void Print(this Person per,string s)40     {41       Console.WriteLine($"调用的是CustomNamaspace命名空间下的扩展方法暑促:姓名为:{per.Name},附加字符串{s}");42     }43   }44 45 }

    在以上代码中,存在两个不同的命名控件,她们都定义了带一个参数的扩展方法Print。根据前面对编译器调用方法的优先级的分析,编译器首先查看Person类型中是否定义了无参的Print实例方法。如果有,则停止查找;否则继续查找当前命名空间下,即CurrentNamespace下是否定义了带一个参数的扩展方法Print。

    注意:(1)如果扩展的类型中定义了无参数的Print的实例方法,则在p后面键入“.”运算符时,VS的智能提示将不会给出扩展方法。

             (2)如果同一个命名空间下的两个类中含有扩展类型相同的方法,编译器便不知道该调用哪个方法了,就会出现编译错误。

 

4、空引用也可调用扩展方法

     4.1 拿例子说话

           

 1 namespace 扩展方法3 2 { 3   class Program 4   { 5     static void Main(string[] args) 6     { 7       Console.WriteLine("空引用上调用扩展方法演示:"); 8       string s = null; 9       Console.WriteLine($"字符串S为空字符串:{s.IsNull()}");10       Console.ReadKey();11     }12   }13 14   public static class NullExtern15   {16     public static bool IsNull(this object obj)17     {18       return obj == null;19     }20   }21 }

    以上的代码没有报异常,可以正常运行。不过在上面的代码中,代码扩展了object类型,所有继承于object的类型都将具有该扩展方法,这就对其他子类型产生了“污染”。

更好的实现方式应该是:

1 public static bool isNull(this string str)2 {3   return str==null; 4 }

    所以当我们为某一个类型定义扩展方法时,应尽量扩展具体的类型,而不要扩展其基类。在空引用上调用扩展方法之所以不会出现NullReferenceException异常,是因为对于编译器而言,这个过程只是把空引用"S"当成参数传入静态方法而已,即s.IsNull的调用等效于下面代码:Console.WriteLine($"字符串s为空字符串{NullExten.IsNull(s)}");这并不是真正地在空引用上调用方法,所以也就不存在异常的问题。