深入理解扩展方法

     在 Java 中没有这样的东西,一个类一旦是 final 的 ,这个类就不能再被添加方法, 但是 C# 能够做到,可以给 sealed 类添加新的方法,这点我还是比较喜欢 c# 的。这就是 C# 中的扩展方法。

那么什么情况下我们才需要去给一个类写扩展方法呢?

  1. 系统自带的类型,我们无法去修改;
  2. 修改源代码需要较大的精力,而且可能会带来错误;
  3. 我们只是需要一个或者较少的几个方法,修改源代码费时费力;
  4. 被扩展的类是 sealed 的,不能被继承;(就算不是 sealed 的,我们也不能因为需要一个方法而去写一个子类,这样不是面向对象)

下面是扩展方法的三个要素:(也算是语法规则)

  1. 它必须在一个非嵌套、非泛型的静态类
  2. 扩展方法必须是一个静态方法;
  3. 第一个参数必须加上this关键字作为前缀(第一个参数类型也称为扩展类型,即指方法对这个类型进行扩展);
  4. 第一个参数不能用其他任何修饰符(如不能使用ref out等修饰符)
  5. 第一个参数的类型不能是指针类型

下面就举个例子:

我们一般将字符串类型的数字转换为int类型,一般都是用的 int.Parse() 方法,或者 Convert类的方法,我们能不能给 string 类型添加一个 Parse方法呢?

当然是可以的,代码上来先:(这里只写了无参数的扩展方法,有参数的直接在参数列表中添加即可,调用时传递对应参数)

public static class StringExt {
    static private Regex regexNumber = new Regex("\\d+");
    static public bool IsNumber(this string input)
    {
        if (string.IsNullOrEmpty(input))
        {
            return false;
        }
        return regexNumber.IsMatch(input);
    }
}

在String实例上调用这个方法

var abc = “123”;
var isNumber = abs.IsNumber();

       有一点可能不好理解,为什么参数列表里面有参数,但是在调用的时候却不传递参数,对于这点我之前也是有点迷糊,但是想通了就好了,那里不是有个 this 关键字吗?this指代的就是当前对象嘛, 也就是被扩展类的实例,也就是扩展方法的调用者,既然是调用者,那还把它当参数传,肯定不传呀。

下面写一下扩展方法的特点:

  1. this 关键字紧跟着的不是参数,而是调用者,调用者后面的参数才是扩展方法真正的参数,在调用时必须传递;
  2. 如果被扩展的类中的实例方法和扩展方法的方法签名相同(扩展方法中方法的签名应该要去掉 this 和调用者参数),则优先调用本类中的实例方法;
  3. 被扩展类(可以是普通类,也可以是接口抽象类)的子类对象可以直接调用父类的扩展方法,也就是说子类也继承了父类的扩展方法;
  4. 这点算是第 3 点的补充,只有被扩展类的本类对象或者子类对象,才能调用扩展方法;
using System;

namespace 扩展方法如何被发现Demo
{
    // 要使用不同命名空间的扩展方法首先要添加该命名空间的引用
    using CustomNamesapce;
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person { Name = "Learning hard" };
            // 当类型中包含了实例方法时,VS中的智能提示就只会列出实例方法,而不会列出扩展方法
            // 当把实例方法注释掉之后,VS的智能提示中才会列出扩展方法,此时编译器在Person类型中找不到实例方法
            // 所以首先从当前命名空间下查找是否有该名字的扩展方法,如果找到不会去其他命名空间中查找了
            // 如果在当前命名空间中没有找到,则会到导入的命名空间中再进行查找
            p.Print();
            p.Print("Hello");
            Console.Read();
        }  
    }

    // 自定义类型
    public class Person
    {
        public string Name { get; set; }

         // 当类型中的实例方法
        public void Print()
        {
            Console.WriteLine("调用实例方法输出,姓名为: {0}", Name);
        }
    }

    // 当前命名空间下的扩展方法定义
    public static class Extensionclass
    {
        /// <summary>
        ///  扩展方法定义
        /// </summary>
        /// <param name="per"></param>
        public static void Print(this Person per)
        {
            Console.WriteLine("调用的是同一命名空间下的扩展方法输出,姓名为: {0}", per.Name);
        }
    }
}

namespace CustomNamesapce
{
    using 扩展方法如何被发现Demo;

    public static class CustomExtensionClass
    {
        /// <summary>
        ///  扩展方法定义
        /// </summary>
        /// <param name="per"></param>
        public static void Print(this Person per)
        {
            Console.WriteLine("调用的是不同命名空间下扩展方法输出,姓名为: {0}", per.Name);
        }

        /// <summary>
        ///  扩展方法定义
        /// </summary>
        /// <param name="per"></param>
        public static void Print(this Person per,string s)
        {
            Console.WriteLine("调用的是不同命名空间下扩展方法输出,姓名为: {0}, 附加字符串为{1}", per.Name, s);
        }

    }
}

运行结果:

  当没有注释掉Person类中的实例方法Print时,此时在p后面键入.运算符时,智能提示将不会出现扩展方法(扩展方法前面有一个向下的箭头标示出来的),下面是没有注释实例方法时智能提示的截图(此时智能提示不会反射扩展方法出来):

  并且从上面运行结果可以看出,当调用p.Print()方法时,此时调用的是离该调用较近的命名空间下的Print方法(尽管在CustomNamesapce命名空间下也定义了扩展方法Print)。、然而使用扩展方法还是存在一些问题的,如果同一个命名空间下的两个类都含有扩展类型相同的方法时,此时编译器就没有办法知道调用哪个方法了(这里标示出来引起大家的注意)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值