.NET 扩展方法 (二)

上一篇随笔 .NET 扩展方法 (一) 已经对 扩展方法有了大致的介绍,这篇算是一个补充,让我们来看一下扩展方法的几个细节:

 

一、扩展方法具有继承性

当使用扩展方法扩展一个类型的时候,其也扩展了派生类,所以上一篇的遗留问题“如果给object添加一个扩展方法会出现什么效果呢?” 的

答案就是——所有类型都将扩展该方法。object类已经经受住了时间的考验,我们似乎也找不到更合适的理由来扩展object类。从另外的

角度考虑,如果扩展了object类,很有可能会给“智能敏感提示”造成污染,以至于填充了过多的垃圾信息(因为许多类型也根本用不到该方法)。

 

二、扩展方法允许和扩展类型原有方法相等效的方法存在 (此处是个雷)

由于汉语语言表述的所带来的不易理解性,我们还是直接用代码来解释吧,如下的代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static class StringExtentsion
{
    public static string ToString(this string str)
    {
        return "Extentsion" + str;
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        string str = "test";
 
        Console.WriteLine(str.ToString()); // 输出结果为: test,也就说编译器会优先选用原有类的实例化方法,如果没找到匹配方法再寻找扩展方法
 
        Console.Read();
    }
}

由上述的代码片段可以知:StringExtentsion类中扩展方法ToString 和 String类的原有的ToString方法 对于客户端代码而言,它们的语法表象是

一样的,但本质上一个是StringExtentsion类的静态方法,一个是String类的实例化方法。然而编译运行没有产生错误,更没有产生警告。所以在

这种情况下很容“埋雷”,一不小心就会中招。有人也许会说:我注意一下不要和.NET类库的方法重名就可以了。但是你能保证 .NET 6、甚至.NET 10

的方法名和你写的绝对不重名吗?所以,扩展方法存在着版本控制的问题

 

三、扩展方法允许在两个或多个类中共存相同的扩展方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static class StringExtentsion
{
    public static bool IsEmpty(this string str)
    {
        return string.IsNullOrWhiteSpace(str);
    }
}
 
public static class OtherStringExtentsion
{
    public static bool IsEmpty(this string str)
    {
        return string.IsNullOrWhiteSpace(str);
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        string str = null;
 
        //bool result = str.IsEmpty(); // 这种写法会产生编译错误:不明确的调用,因为它能找到两个扩展方法
 
        bool result = StringExtentsion.IsEmpty(str); // 必须显示使用类调用静态方法
 
        Console.WriteLine(result);
 
        Console.Read();
    }
}

  

四、扩展其他类型的方法

扩展方法不仅可以扩展类类型,而且可以扩展接口、枚举、委托、结构、数组及对应的泛型类型 等类型。

在这里着重说一下扩展接口,任何 “实现了接口的类型对象” 都可以调用接口上的扩展方法。针对这一特点,我们完全可以将实现接口

的共用代码放进扩展方法里,实现代码复用。从形式上来看,该特点是 “单继承 + 接口” 与 “多继承”的中间产物,已经有了“多继承”的影子。

 

五、委托与扩展方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace MethodDemo
{
    public static class StringExtentsion
    {
        public static string ShowString(this string str)
        {
            return "ShowString:" + str;
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            string str = "meng";
 
            Func<string> fun = str.ShowString;
 
            fun();
 
            Func<string> fun2 = str.ToString;
 
            fun2();
 
            Console.Read();
        }
    }
}

  

如果你已经看过了 .NET 扩展方法 (一) ,也许你能猜到我接下来想说什么了吧。没错,看一下IL代码吧,看看编译器在背后搞了哪些“怪”。

顺便借助这个机会,说几个IL指令。

复制代码
 1 .method private hidebysig static void  Main(string[] args) cil managed
 2 {
 3   .entrypoint
 4   // 代码大小       55 (0x37)
 5   .maxstack  2
 6 
 7   // 初始化3个局部变量
 8   .locals init ([0] string str,
 9            [1] class [mscorlib]System.Func`1<string> fun,
10            [2] class [mscorlib]System.Func`1<string> fun2)
11   IL_0000:  nop
12 
13   //将 "meng" 这个字符串对象的引用 入栈
14   IL_0001:  ldstr      "meng"
15 
16   // 将栈顶的值赋值给第0个局部变量(即 str),栈顶值出栈
17   IL_0006:  stloc.0
18 
19   // 将第0个局部变量入栈 (即 str 入栈)
20   IL_0007:  ldloc.0
21 
22   // 将 MethodDemo.StringExtentsion类的静态方法ShowString的指针入栈
23   IL_0008:  ldftn      string MethodDemo.StringExtentsion::ShowString(string)
24 
25   // 调用构造函数 new一个 Func<String>类型的委托
26   IL_000e:  newobj     instance void class [mscorlib]System.Func`1<string>::.ctor(object,
27                                                                                   native int)
28   IL_0013:  stloc.1
29   IL_0014:  ldloc.1
30 
31   // 调用fun对象的Invoke方法
32   IL_0015:  callvirt   instance !0 class [mscorlib]System.Func`1<string>::Invoke()
33   IL_001a:  pop
34   IL_001b:  ldloc.0
35   IL_001c:  dup
36 
37   // 将str 对象的实例化方法ToString方法的指针入栈
38   IL_001d:  ldvirtftn  instance string [mscorlib]System.Object::ToString()
39   IL_0023:  newobj     instance void class [mscorlib]System.Func`1<string>::.ctor(object,
40                                                                                   native int)
41   IL_0028:  stloc.2
42   IL_0029:  ldloc.2
43   IL_002a:  callvirt   instance !0 class [mscorlib]System.Func`1<string>::Invoke()
44   IL_002f:  pop
45   IL_0030:  call       int32 [mscorlib]System.Console::Read()
46   IL_0035:  pop
47   IL_0036:  ret
48 } // end of method Program::Main
复制代码

核心的IL指令已经给予了差不多的注释,根据IL指令可以得出结果:我们又被编译器“欺骗”了一次,fun对象保存的方法指针是

MethodDemo.StringExtentsion类的静态方法ShowString的指针。fun2对象保存的方法的指针str对象的ToString方法的指针。

 

posted @   把爱延续  阅读(1801)  评论(3编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
历史上的今天:
2011-08-28 正则过滤html标记
点击右上角即可分享
微信分享提示