扩展方法用法及其原理和注意事项

前言

一直以来尤其像C#一些常见的语法,本人更愿意去探讨其内部实现的原理,为什么要这么做呢?只是为了当我真正在开发中运用语法的时候不会因为犯常识性错误或者说因为一些注意事项未曾注意到而耽误一些无谓的时间,同时也能理解的更深入而不是仅仅停留在表面(或许理解也不是太透)。(当然本人能力有限,太高深的东西必定是研究不明白了,也只有这能力了)。

概念

扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。扩展方法是在C#3.0中添加的特性。

引入

听起来有点迷糊,也许你无意之间就在用扩展方法,请看下图:

上图中有虚线下角标的方法就是扩展方法,因为其实现了IENumerable<T>接口,所以会有这些方法。那到底我们手动怎么实现呢?

实现

添加一个Person类,代码如下:

    public class Person
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public bool Gender { get; set; }
    }

现在假设如下场景:如果该项目中关于Person类字段和方法都已确定,但是在该项目完成之前boss发话要给Person添加一个需求,添加一个Preson类共有的 Hobby 方法。由于项目架构都已经确定,所以为了在不修改源代码的情况下,我们用 扩展方法 来实现。我们新添加一个扩展类 PersonExtension ,添加Hobby方法,如下:

    public static class PersonExtension
    {
        public static void Hobby(this Person p)
        {
            Console.WriteLine("我们都有爱好");
        }
    }

此时我们在控制台实例化对象试试进行访问该扩展方法Hobby,结论当然是能:

结果打印出 我们都有爱好 !至此我们就完成了扩展方法的基本使用,感觉是不是so easy!但是我就有如下几个疑问:

(1)怎么知道我们编写的是扩展方法的呢?

(2)编写的扩展方法编译器到底是怎么找到的呢?

(3)是实例方法先执行还是扩展方法先执行呢?

(4)这个 this关键字到底有什么作用呢?

(5)编写扩展方法时我们又应该注意些什么呢?

请看下文,下面我们一一进行探讨并解决。

实现原理

说到看其原理,依然是借助高大上的反编译工具了。我们看 p.Hobby(); 这段代码对应的IL代码是什么就够了,请看其对应的IL代码:

最终调用的依然是调用 PersonExtension.Hobby() ,说明 扩展方法其本质是静态方法 看来并没有什么特别之处。在Pserson中时常看见这样的构造函数

        public Person(string name, int age)
        {
            this.Name = name;
            this.Age = age;
        }

所以从中我们也可以得知,谁实例化了Person谁就指代this,那同样可以理解扩展方法中的this,就是p实例化了Person那这个this就是指向P了,有时候想想语言有时候确实是相通的,此时我想到在JavaScript中不是有个Call方法吗,如果不清楚的话,我拷贝了正在学习中的JavaScript代码,如下:

                function person(age,name){
                    this.age=age;
                    this.name=name;
                }

                var obj=new Object();
                person.call(obj,12,‘小黑‘);
                console.log(obj.age);
                console.log(obj.name);

               /*
               创建空对象obj,此时调用person类的call()方法,并将obj传递进去,此时obj成为其调用上下文,此时this即obj,最终能打印出12和小黑
               */

不难看出实例化的对象obj通过call方法,就指向了person中this!我们回到  p.Hobby(); 等价于 PersonExtension.Hobby(p) ,就是将你实例化的对象传到了扩展方法中的this中。那么问题来了,既然扩展方法也是方法,如果我们在Person类中定义一个方法那先是访问你定义的实例方法还是先访问你定义的扩展方法呢?这个疑问先遗留在这里,我们继续往下看,我们刚才看到 p.Hobby()实际上是调用的PersonExtension.Hobby(); 那我们接下来看看 PersonExtension.Hobby() 这个方法里面到底有什么呢?其IL代码如下:

我们注意到这个里面有个 ExtensionAttribute 标记,就字面意思就叫做【扩展特性】吧,那我们猜想一下,是不是是不是通过这个【扩展特性】来实现的扩展方法了,我们反过来看,显然我们实现了扩展方法,里面肯定有这个特性所在的程序集,要是我们可以将其特性所在的程序集进行移除,生成错误的话,是不是就说明我们的论断正确呢?试试即可,将会报错如下:

无法定义新的扩展方法,因为找不到编译器所需的类型“System.Runtime.CompilerServices.ExtensionAttribute。”是否缺少引用?

所以通过上述我们知道了通过添加 System.Runtime.CompilerServices 程序集里的 ExtensionAttribute 来实现扩展方法。此时不仅心生疑问,用它来实现扩展方法,那么反问一句,它怎么知道它是扩展方法的呢?此时只剩this关键字了,于是我去掉扩展方法中的 this 关键字重新生成试试,结果如下:

这就说明通过 this 关键字来识别为扩展方法,同时此时运行时编译器无法识别此语法,所以编译器则将通过上述IL代码中的特性来转换为运行时识别的语法。

接下来问题又来了 ,那它是先执行实例方法还是先执行扩展方法呢?我们接下来,在该类中添加同名的实例方法Hobby(),如下:

        public void Hobby()
        {
            Console.WriteLine("这是实例中的我们都有爱好");
        }

此时我们再调用此方法,结果在控制台打印如下:

说明 实例方法优先于扩展方法执行并且可以有同名的实例方法和扩展方法 !那么问题又来了,它是怎么找到扩展方法的呢?我们知道当调用此p.Hobby()方法时,首先肯定会去p对象去找Hobby()方法,如果有,则调用它的实例方法,如果未找到此时再去找扩展方法,问题是我们只知道通过 this 来标识为扩展方法,这个时候就得看我们的约定了,静态方法所在的类必须是静态类,我们可以想象,如果是普通类的话是不是得一个一个的找,这样岂不是很消耗性能,这是微软大大想要看到的吗?当然不是,要是为静态类,只需要到静态类去找再去静态方法去找标识为this关键字的方法,这时找到了就说明这个方法是扩展方法。

注意事项

(1)

我们再定义一个Bob类,继承该Person类,并在控制台尝试调用该扩展方法,代码如下

    public class Bob : Person
    {

    }

    Bob b = new Bob();
    b.Hobby();

结果是可以访问的,说明: 扩展方法能够被继承

(2)

当我们将扩展方法的访问修饰符修改成private会出错,如下:

说明 其访问修饰符必须是public

(3)

当我们在控制台尝试传入空引用对象调用该扩展方法时,扩展方法和控制台调用代码如下,看看结果如何:

    public static class PersonExtension
    {
        public static void Hobby(this Person p)
        {
            Console.WriteLine("我们都有爱好");
        }
    }

    Person p = null;
    p.Hobby();

此时正常调用,说明空引用可以调用扩展方法,但是将扩展方法修改如下,则会出错:未将对象设置到对象的实例。

    public static class PersonExtension
    {
        public static void Hobby(this Person p)
        {
            Console.WriteLine(p.Name);
        }
    }

说明 允许空引用调用扩展方法,若扩展方法中使用了传入的实例成员,则会出现异常

总结

声明扩展方法的必须条件

方法必须定义在顶级的静态类中,并且该静态类必须直接处在命名空间下而且不能为泛型类(即方法必须放在非嵌套、非泛型的静态类中)

方法必须是静态的并且第一个参数用this关键字修饰,这个参数被叫做【参数实例】

方法的访问修饰符必须是public

至少有一个参数

不能有其他参数修饰第一个参数(如ref,out等等)

第一个参数的类型不能是指针类型

注意事项

实例方法优先于扩展方法执行(允许有同名的实例方法和扩展方法)

在空引用上可以调用扩展方法

扩展方法能够被继承

扩展方法相关原理

其本质是静态方法

this关键字的作用:(1)指向当前扩展方法中第一个参数类型的实例(2)作为标记,标记此方法为扩展方法

查找扩展方法:如果对象的实例中有该实例方法,调用它的实例方法,如果未找到此时再去找扩展方法,问题是我们只知道通过 this 来标识为扩展方法,这个时候就得看我们的约定了,只需要到静态类去找再去静态方法去找标识为this关键字的方法,这时找到了就说明这个方法是扩展方法。

时间: 2024-10-19 19:58:02

扩展方法用法及其原理和注意事项的相关文章

扩展方法用法整理

扩展方法貌似平时很少用,平时基本都是用静态方法,其实静态方法也挺方便的. class Program { static void Main(string[] args) { var p = new Person() { BirthTime = DateTime.Parse("1990-07-19") }; var age = p.GetAge();//扩展方法调用起来更顺眼 age = ExtensionClass.GetAge2(p);//静态方法调用 Console.ReadKey

C#复习笔记(4)--C#3:革新写代码的方式(扩展方法)

扩展方法 扩展方法有以下几个需求: 你想为一个类型添加一些 成员: 你不需要为类型的实例添加任何更多的数据: 你不能改变类型本身, 因为是别人的代码. 对于C#1和C#2中的静态方法,扩展方法是一种更优雅的解决方案. 语法 并不是任何方法都能作为扩展方法使用-- 它必须具有以下特征: 它必须在一个非嵌套的. 非泛型的静态类中( 所以必须是一 个静态方法): 它至少要有 一个参数: 第一个参数必须附加 this 关键字作为前缀: 第一个参数不能有其他任何修饰 符(比如out或ref): 第一个参数

C#扫盲之:带你掌握C#的扩展方法、以及探讨扩展方法的本质、注意事项

1.为什么需要扩展方法 .NET3.5给我们提供了扩展方法的概念,它的功能是在不修改要添加类型的原有结构时,允许你为类或结构添加新方法. 思考:那么究竟为什么需要扩展方法呢,为什么不直接修改原有类型呢? 首先,假设我们的项目中有一个类,后来过了一段时间,我们明确的知道需要为该类添加一个新功能,考虑这个需求有两个解决办法: (1)直接修改当前类的定义 这样做的缺点是,破坏向后的兼容性,可能以前使用的旧代码无法通过编译.比如说旧代码使用了一个Methed(int,int)的方法,但是为了满足新功能我

【转载】C#扫盲之:带你掌握C#的扩展方法、以及探讨扩展方法的本质、注意事项

1.为什么需要扩展方法 .NET3.5给我们提供了扩展方法的概念,它的功能是在不修改要添加类型的原有结构时,允许你为类或结构添加新方法. 思考:那么究竟为什么需要扩展方法呢,为什么不直接修改原有类型呢? 首先,假设我们的项目中有一个类,后来过了一段时间,我们明确的知道需要为该类添加一个新功能,考虑这个需求有两个解决办法: (1)直接修改当前类的定义 这样做的缺点是,破坏向后的兼容性,可能以前使用的旧代码无法通过编译.比如说旧代码使用了一个Methed(int,int)的方法,但是为了满足新功能我

[原创]扩展方法基本用法

前言 首先我们看看msdn上面的解释:扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型.重新编译或以其他方式修改原始类型. 其实说白了就是微软那班哥们写好的类库,我们无法修改,但是我们可以给这些类库通过一种方式添加我们的一些方法. 1.0 扩展方法应用场景 DateTime now = DateTime.Now; //1.0 将now格式化成yyyy-MM-dd 输出 string fmtstr = now.Tostring("yyyy-MM-dd");//2.0 将no

JavaScript中this的工作原理以及注意事项

在JavaScript中,this 的概念比较复杂.除了在面向对象编程中,this 还是随处可用的.这篇文章介绍了this 的工作原理,它会造成什么样的问题以及this 的相关例子. 要根据this 所在的位置来理解它,情况大概可以分为3种: 1.在函数中:this 通常是一个隐含的参数. 2.在函数外(顶级作用域中):在浏览器中this 指的是全局对象:在Node.js中指的是模块(module)的导出(exports). 3.传递到eval()中的字符串:如果eval()是被直接调用的,th

DisplayNameFor()方法的工作原理

DisplayNameFor()方法的工作原理原创Peter Yelnav 最后发布于2018-11-23 11:09:51 阅读数 1308 收藏展开最近研究了一下ASP.NET MVC,困惑于视图中DisplayNameFor()方法,于是粗略探究了一下.观点浅显,如有错误之处,还请各位大神多多指正. 完整代码可以到Microsoft Doc / ASP.NET / ASP.NET MVC中查看,链接如下:https://docs.microsoft.com/en-us/aspnet/mvc

C#的扩展方法解析

在使用面向对象的语言进行项目开发的过程中,较多的会使用到"继承"的特性,但是并非所有的场景都适合使用"继承"特性,在设计模式的一些基本原则中也有较多的提到. 继承的有关特性的使用所带来的问题:对象的继承关系实在编译时就定义好了,所以无法在运行时改变从父类继承的实现.子类的实现与它父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化.当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写它或被其他更适合的类替换,这种依赖关系限制

泛型List集合转化为DateTable的扩展方法

文章出处:http://www.codeproject.com/Tips/867866/Extension-Method-for-Generic-List-Collection-to-Da 这段代码是能够帮助你把泛型集合List转出成DataTable的扩展方法. 背景: 不知道你是否知道这个扩展方法,但是你可以不做任何修改的去使用下面这个类的代码. 使用代码:   using System; using System.Collections.Generic; using System.Comp