zoukankan      html  css  js  c++  java
  • 使类的扩展更简单——扩展方法

    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)}");这并不是真正地在空引用上调用方法,所以也就不存在异常的问题。

  • 相关阅读:
    Mac zsh: command not found zsh 所有命令在终端失效
    Java根据FreeMarker模板生成Word(doc)文档(带图片)
    2021年Java面试总结——自我篇
    toArray转换踩坑 java.lang.ClassCastException
    并发和并行
    protoBuf3学习
    StringBuffer和StringBuilder区别
    深拷贝和浅拷贝
    从不订购的客户
    使用jenkins遇到的问题汇总
  • 原文地址:https://www.cnblogs.com/Helius/p/5774745.html
Copyright © 2011-2022 走看看