使类的扩展更简单——扩展方法

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                 else
20                 {
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 Person
15     {
16         public string Name { get; set; }
17     }
18 
19     public static class Extensionclass
20     {
21         public static void Print(this Person per)
22         {
23             Console.WriteLine($"调用的是当前命名空间下的扩展方法输出,姓名为:{per.Name}");
24         }
25     }
26 }
27 
28 namespace 扩展方法3
29 {
30     using 扩展方法2;
31 
32     public static class CustomExtensionClass
33     {
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 NullExtern
15     {
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)}");这并不是真正地在空引用上调用方法,所以也就不存在异常的问题。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值