|
@@ -0,0 +1,111 @@
|
|
1
|
+# 扩展方法
|
|
2
|
+- 最常见的是在LINQ中使用扩展方法。
|
|
3
|
+
|
|
4
|
+> 概念
|
|
5
|
+- 扩展方法是一种特殊类型的静态方法。对于一个C#类型,如类(包括密封类)、值类型、接口等,扩展方法可以在不改变类型源码的前提下,为它的实例提供新的成员。
|
|
6
|
+
|
|
7
|
+> 如何定义
|
|
8
|
+- 创建扩展方法很简单,有以下步骤
|
|
9
|
+ - 创建一个静态类;
|
|
10
|
+ - 在其中创建一个静态方法;
|
|
11
|
+ - 为这个静态方法添加至少一个参数,并在第一个参数前加上this关键字,这个关键字会告诉编译器当前方法是一个扩展方法。而这个方法将成为第一个参数所属类型的新成员。
|
|
12
|
+
|
|
13
|
+- 如例,下面的扩展方法,用于枚举值提供一个可获取其DescriptionAttribute特性值的方法:
|
|
14
|
+```
|
|
15
|
+namespace TLA.Infrastructure.Extensions
|
|
16
|
+{
|
|
17
|
+ public static class EnumExtensions
|
|
18
|
+{
|
|
19
|
+ public static string GetDescription(this Enum en)
|
|
20
|
+ {
|
|
21
|
+ Type type=en.GetType();
|
|
22
|
+ MemberInfo[] memInfo=type.GetMember(en.ToString());
|
|
23
|
+ if(memInfo!=null&&memInfo.Length>0)
|
|
24
|
+ {
|
|
25
|
+ object[] attrs=memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute),false);
|
|
26
|
+ if(attrs !=null && attrs.Length>0)
|
|
27
|
+ {
|
|
28
|
+ return((DescriptionAttribute)attrs[0]).Description;
|
|
29
|
+ }
|
|
30
|
+ return en.ToString();
|
|
31
|
+ }
|
|
32
|
+ }
|
|
33
|
+}
|
|
34
|
+}
|
|
35
|
+
|
|
36
|
+```
|
|
37
|
+- 注意:只有在引用扩展方法所在的静态类的命名空间后,才能使用扩展方法,否则,直接调用会编译失败。在上例中,使用该扩展方法要引用TLA.Infrastructure.Extensions命名空间。
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+> 如何使用
|
|
41
|
+- 扩展方法可以用在以下几种场合:
|
|
42
|
+ - 要为某个类型扩展功能,但没有其源码,比如某个款哪个家或第三方库中的一个类;例如,想要获取一个列表中的所有的奇数项,就可以为IList<T>接口增加一个扩展方法,这里的IList<T>接口本身是.NET框架中的接口。
|
|
43
|
+
|
|
44
|
+```
|
|
45
|
+public static class IListExtentions
|
|
46
|
+{
|
|
47
|
+ public static IEnumerable<T> OddItems<T>(this IEnumerable<T> list)
|
|
48
|
+ {
|
|
49
|
+ if(list==null)
|
|
50
|
+ {
|
|
51
|
+ throw new ArgumentNullException(nameof(list));
|
|
52
|
+ }
|
|
53
|
+ for (int i=0;i<list.Count();i++>)
|
|
54
|
+ {
|
|
55
|
+ if(i%2==0)
|
|
56
|
+ {
|
|
57
|
+ yield return list.ElementAt(i);
|
|
58
|
+ }
|
|
59
|
+ }
|
|
60
|
+ }
|
|
61
|
+}
|
|
62
|
+```
|
|
63
|
+- 即使可以访问原有类型的源码,也可以使用扩展方法为它添加辅助功能;
|
|
64
|
+```
|
|
65
|
+public interface ILog
|
|
66
|
+{
|
|
67
|
+ void Log(string message,LogLevel logLevel);
|
|
68
|
+}
|
|
69
|
+public static class ILogExtensions
|
|
70
|
+{
|
|
71
|
+ public static void LogDebug(this ILog logger,string message)
|
|
72
|
+ {
|
|
73
|
+ if(true)
|
|
74
|
+ {
|
|
75
|
+ logger?.Log($"{message}",LogLevel.Debug);
|
|
76
|
+ }
|
|
77
|
+ }
|
|
78
|
+}
|
|
79
|
+```
|
|
80
|
+- 重用代码,使代码更简洁;由于扩展方法封装了一段完整的逻辑,所以,使用扩展方法就避免了复制粘贴代码的情况。上例中扩展方法的内容也符合这种使用的场景。
|
|
81
|
+
|
|
82
|
+> 注意事项
|
|
83
|
+- 扩展方法的本质是为原有类型提供辅助功能,因此,在创建时,要确保它具有实际意义,且遵循单一职责原则;也即,不能过度使用扩展方法并且它能够完成一个具体、完整的功能;
|
|
84
|
+- 扩展方法本身具有通用性,因此,它里面应避免特定的业务数据类型及其相关逻辑;
|
|
85
|
+- 如果为接口增加扩展方法,扩展方法的命名空间可以与接口一致;否则,应尽量避免与原类型写在同一个命名空间下,这样会“污染”原类型。建议做法是为扩展方法所在的类设定一个单独的命名空间,如:<Company>.<Product>.Extentions。不过,这样做也有缺点;在操作原有类型的实例时,如果不引用扩展方法所在的命名空间,那么,它就不容易被发现,而解决这个问题的方法是,尽量将扩展方法文档化,并告诉项目组的其他开发人员
|
|
86
|
+- 为接口增加扩展方法后,则所有实现此接口的类都会包含该扩展方法;
|
|
87
|
+- 在扩展方法中,要对第一个参数进行非空检查,如果为空,应抛出ArgumentNullException异常。
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+> 总结
|
|
91
|
+- 声明扩展方法的必须条件
|
|
92
|
+ - 方法必须定义在顶级的静态中,并且该静态类必须直接处在命名空间下而且不能为泛型类(即方法必须放在非嵌套、非泛型的静态中)
|
|
93
|
+ - 方法必须是静态的并且第一个参数用this关键字修饰,这个参数被叫做【参数实例】
|
|
94
|
+ - 方法的访问修饰符必须是public
|
|
95
|
+ - 至少有一个参数
|
|
96
|
+ - 不能有其他参数修饰第一个参数(如ref,out)
|
|
97
|
+ - 第一个参数的类型不能是指针类型
|
|
98
|
+
|
|
99
|
+- 注意事项
|
|
100
|
+ - 实例方法优于扩展方法执行(允许有同名的实例方法和扩展方法)
|
|
101
|
+ - 在空引用上可以调用扩展方法。
|
|
102
|
+ - 扩展方法能够被继承
|
|
103
|
+
|
|
104
|
+- 扩展方法相关原理
|
|
105
|
+ - 其本质是静态方法
|
|
106
|
+ - this关键字的作用:(1)指向当前扩展方法中第一个参数类型的实例(2)作为标记此方法为扩展方法
|
|
107
|
+ - 查找扩展方法:如果对象的实例中有该实例方法,调用它的实例方法,如果未找到此时再去找扩展方法,问题是我们只知道通过this来标识为扩展方法,这个时候就得看我们的约定了,只需要到静态类去找再去静态方法去找标识为this关键字的方法,这时找到了就说明这个方法是扩展方法。
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|