前言
本節重點不講反射機制,而是講lambda表達式樹來替代反射中常用的獲取屬性和方法,來達到相同的效果但卻比反射高效。
每個人都知道,用反射調用一個方法或者對屬性執行setvalue和getvalue操作的時候都會比直接調用慢很多,這其中設計到clr中內部的處理,不做深究。然而,我們在某些情況下又無法不使用反射,比如:在一個orm框架中,你要將一個datarow轉化為一個對象,但你又不清楚該對象有什么屬性,這時候你就需要寫一個通用的泛型方法來處理,以下代碼寫得有點惡心,但不妨礙理解意思:
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
//將datareader轉化為一個對象 private static t getobj<t>(sqlitedatareader reader) where t : class { t obj = new t(); propertyinfo[] pros = obj.gettype().getproperties(); foreach (propertyinfo item in pros) { try { int32 index = reader.getordinal(item.name); string result = reader.getstring(index); if ( typeof ( string ) == item.propertytype) { item.setvalue(obj, result); continue ; } if ( typeof (datetime) == item.propertytype) { item.setvalue(obj, convert.todatetime(result)); continue ; } if ( typeof (boolean) == item.propertytype) { item.setvalue(obj, convert.toboolean(result)); continue ; } if ( typeof (int32) == item.propertytype) { item.setvalue(obj, convert.toint32(result)); continue ; } if ( typeof (single) == item.propertytype) { item.setvalue(obj, convert.tosingle(result)); continue ; } if ( typeof (single) == item.propertytype) { item.setvalue(obj, convert.tosingle(result)); continue ; } if ( typeof ( double ) == item.propertytype) { item.setvalue(obj, convert.todouble(result)); continue ; } if ( typeof ( decimal ) == item.propertytype) { item.setvalue(obj, convert.todecimal(result)); continue ; } if ( typeof ( byte ) == item.propertytype) { item.setvalue(obj, convert.tobyte(result)); continue ; } } catch (argumentoutofrangeexception ex) { continue ; } } return obj; } |
對于這種情況,其執行效率是特別低下的,具體多慢在下面例子會在.net core平臺上和.net framework4.0運行測試案例.對于以上我舉例的情況,效率上我們還可以得到提升。但對于想在運行時修改一下屬性的名稱或其他操作,反射還是一項特別的神器,因此在某些情況下反射還是無法避免的。
但是對于只是簡單的setvalue或者getvalue,包括用反射構造函數,我們可以想一個中繼的方法,那就是使用表達式樹。對于不理解表達式樹的,可以到微軟文檔查看,。表達式樹很容易通過對象模型表示表達式,因此強烈建議學習。查看以下代碼:
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
|
static void main() { dog dog = new dog(); propertyinfo propertyinfo = dog.gettype().getproperty(nameof(dog.name)); //獲取對象dog的屬性 methodinfo settermethodinfo = propertyinfo.getsetmethod(); //獲取屬性name的set方法 parameterexpression param = expression.parameter( typeof (dog), "param" ); expression getpropertyvalueexp = expression.lambda(expression.property(param, nameof(dog.name)), param); expression<func<dog, string >> getpropertyvaluelambda = (expression<func<dog, string >>)getpropertyvalueexp; parameterexpression paramo = expression.parameter( typeof (dog), "param" ); parameterexpression parami = expression.parameter( typeof ( string ), "newvalue" ); methodcallexpression methodcallsetterofproperty = expression.call(paramo, settermethodinfo, parami); expression setpropertyvalueexp = expression.lambda(methodcallsetterofproperty, paramo, parami); expression<action<dog, string >> setpropertyvaluelambda = (expression<action<dog, string >>)setpropertyvalueexp; //創建了屬性name的get方法表達式和set方法表達式,當然只是最簡單的 func<dog, string > getter = getpropertyvaluelambda.compile(); action<dog, string > setter = setpropertyvaluelambda.compile(); setter?.invoke(dog, "wlj" ); //我們現在對dog這個對象的name屬性賦值 string dogname = getter?.invoke(dog); //獲取屬性name的值 console.writeline(dogname); console.readkey(); } public class dog { public string name { get ; set ; } } |
以下代碼可能很難看得懂,但只要知道我們創建了屬性的get、set這兩個方法就行,其結果最后也能輸出狗的名字 wlj,擁有expressiontree的好處是他有一個名為compile()的方法,它創建一個代表表達式的代碼塊。現在是最有趣的部分,假設你在編譯時不知道類型(在這篇文章中包含的代碼我在不同的程序集上創建了一個類型)你仍然可以應用這種技術,我將對于常用的屬性的set,get操作進行分裝。
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
/// <summary> /// 屬性類,仿造反射中的propertyinfo /// </summary> public class property { private readonly propertygetter getter; private readonly propertysetter setter; public string name { get ; private set ; } public propertyinfo info { get ; private set ; } public property(propertyinfo propertyinfo) { if (propertyinfo == null ) throw new nullreferenceexception( "屬性不能為空" ); this .name = propertyinfo.name; this .info = propertyinfo; if ( this .info.canread) { this .getter = new propertygetter(propertyinfo); } if ( this .info.canwrite) { this .setter = new propertysetter(propertyinfo); } } /// <summary> /// 獲取對象的值 /// </summary> /// <param name="instance"></param> /// <returns></returns> public object getvalue( object instance) { return getter?.invoke(instance); } /// <summary> /// 賦值操作 /// </summary> /// <param name="instance"></param> /// <param name="value"></param> public void setvalue( object instance, object value) { this .setter?.invoke(instance, value); } private static readonly concurrentdictionary<type, core.reflection.property[]> securitycache = new concurrentdictionary<type, property[]>(); public static core.reflection.property[] getproperties(type type) { return securitycache.getoradd(type, t => t.getproperties().select(p => new property(p)).toarray()); } } /// <summary> /// 屬性get操作類 /// </summary> public class propertygetter { private readonly func< object , object > funcget; public propertygetter(propertyinfo propertyinfo) : this (propertyinfo?.declaringtype, propertyinfo.name) { } public propertygetter(type declaretype, string propertyname) { if (declaretype == null ) { throw new argumentnullexception(nameof(declaretype)); } if (propertyname == null ) { throw new argumentnullexception(nameof(propertyname)); } this .funcget = creategetvaluedeleagte(declaretype, propertyname); } //代碼核心部分 private static func< object , object > creategetvaluedeleagte(type declaretype, string propertyname) { // (object instance) => (object)((declaringtype)instance).propertyname var param_instance = expression.parameter( typeof ( object )); var body_objtotype = expression.convert(param_instance, declaretype); var body_gettypeproperty = expression.property(body_objtotype, propertyname); var body_return = expression.convert(body_gettypeproperty, typeof ( object )); return expression.lambda<func< object , object >>(body_return, param_instance).compile(); } public object invoke( object instance) { return this .funcget?.invoke(instance); } } public class propertysetter { private readonly action< object , object > setfunc; public propertysetter(propertyinfo property) { if (property == null ) { throw new argumentnullexception(nameof(property)); } this .setfunc = createsetvaluedelagate(property); } private static action< object , object > createsetvaluedelagate(propertyinfo property) { // (object instance, object value) => // ((instancetype)instance).set_xxx((propertytype)value) //聲明方法需要的參數 var param_instance = expression.parameter( typeof ( object )); var param_value = expression.parameter( typeof ( object )); var body_instance = expression.convert(param_instance, property.declaringtype); var body_value = expression.convert(param_value, property.propertytype); var body_call = expression.call(body_instance, property.getsetmethod(), body_value); return expression.lambda<action< object , object >>(body_call, param_instance, param_value).compile(); } public void invoke( object instance, object value) { this .setfunc?.invoke(instance, value); } } |
在將代碼應用到實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
dog dog = new dog(); propertyinfo propertyinfo = dog.gettype().getproperty(nameof(dog.name)); //反射操作 propertyinfo.setvalue(dog, "wlj" ); string result = propertyinfo.getvalue(dog) as string ; console.writeline(result); //表達式樹的操作 property property = new property(propertyinfo); property.setvalue(dog, "wlj2" ); string result2 = propertyinfo.getvalue(dog) as string ; console.writeline(result2); |
發現其實現的目的與反射一致,但效率卻有明顯的提高。
以下測試以下他們兩之間的效率。測試代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
student student = new student(); propertyinfo propertyinfo = student.gettype().getproperty(nameof(student.name)); property expproperty = new property(propertyinfo); int32 loopcount = 1000000; codetimer.initialize(); //測試環境初始化 //下面該方法個執行1000000次 codetimer.time( "基礎反射" , loopcount, () => { propertyinfo.setvalue(student, "fode" , null ); }); codetimer.time( "lambda表達式樹" , loopcount, () => { expproperty.setvalue(student, "fode" ); }); codetimer.time( "直接賦值" , loopcount, () => { student.name = "fode" ; }); console.readkey(); |
其.net4.0環境下運行結果如下:
.net core環境下運行結果:
從以上結果可以知道,迭代同樣的次數反射需要183ms,而用表達式只要34ms,直接賦值需要7ms,在效率上,使用表達式這種方法有顯著的提高,您可以看到使用此技術可以完全避免使用反射時的性能損失。反射之所以效率有點低主要取決于其加載的時候時在運行期下,而表達式則在編譯期,下篇有空將會介紹用emit技術優化反射,會比表達式略快一點。
注:對于常用對象的屬性,最好將其緩存起來,這樣效率會更高。。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://www.cnblogs.com/fode/p/10079630.html