利用lambda表達(dá)式樹優(yōu)化反射詳解
前言
本節(jié)重點(diǎn)不講反射機(jī)制,而是講lambda表達(dá)式樹來(lái)替代反射中常用的獲取屬性和方法,來(lái)達(dá)到相同的效果但卻比反射高效。
每個(gè)人都知道,用反射調(diào)用一個(gè)方法或者對(duì)屬性執(zhí)行SetValue和GetValue操作的時(shí)候都會(huì)比直接調(diào)用慢很多,這其中設(shè)計(jì)到CLR中內(nèi)部的處理,不做深究。然而,我們?cè)谀承┣闆r下又無(wú)法不使用反射,比如:在一個(gè)ORM框架中,你要將一個(gè)DataRow轉(zhuǎn)化為一個(gè)對(duì)象,但你又不清楚該對(duì)象有什么屬性,這時(shí)候你就需要寫一個(gè)通用的泛型方法來(lái)處理,以下代碼寫得有點(diǎn)惡心,但不妨礙理解意思:
//將DataReader轉(zhuǎn)化為一個(gè)對(duì)象
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;
}
對(duì)于這種情況,其執(zhí)行效率是特別低下的,具體多慢在下面例子會(huì)在.Net Core平臺(tái)上和.Net Framework4.0運(yùn)行測(cè)試案例.對(duì)于以上我舉例的情況,效率上我們還可以得到提升。但對(duì)于想在運(yùn)行時(shí)修改一下屬性的名稱或其他操作,反射還是一項(xiàng)特別的神器,因此在某些情況下反射還是無(wú)法避免的。
但是對(duì)于只是簡(jiǎn)單的SetValue或者GetValue,包括用反射構(gòu)造函數(shù),我們可以想一個(gè)中繼的方法,那就是使用表達(dá)式樹。對(duì)于不理解表達(dá)式樹的,可以到微軟文檔查看,點(diǎn)擊我。表達(dá)式樹很容易通過對(duì)象模型表示表達(dá)式,因此強(qiáng)烈建議學(xué)習(xí)。查看以下代碼:
static void Main()
{
Dog dog = new Dog();
PropertyInfo propertyInfo = dog.GetType().GetProperty(nameof(dog.Name)); //獲取對(duì)象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;
//創(chuàng)建了屬性Name的Get方法表達(dá)式和Set方法表達(dá)式,當(dāng)然只是最簡(jiǎn)單的
Func<Dog, String> Getter = GetPropertyValueLambda.Compile();
Action<Dog, String> Setter = SetPropertyValueLambda.Compile();
Setter?.Invoke(dog, "WLJ"); //我們現(xiàn)在對(duì)dog這個(gè)對(duì)象的Name屬性賦值
String dogName = Getter?.Invoke(dog); //獲取屬性Name的值
Console.WriteLine(dogName);
Console.ReadKey();
}
public class Dog
{
public String Name { get; set; }
}
以下代碼可能很難看得懂,但只要知道我們創(chuàng)建了屬性的Get、Set這兩個(gè)方法就行,其結(jié)果最后也能輸出狗的名字 WLJ,擁有ExpressionTree的好處是他有一個(gè)名為Compile()的方法,它創(chuàng)建一個(gè)代表表達(dá)式的代碼塊?,F(xiàn)在是最有趣的部分,假設(shè)你在編譯時(shí)不知道類型(在這篇文章中包含的代碼我在不同的程序集上創(chuàng)建了一個(gè)類型)你仍然可以應(yīng)用這種技術(shù),我將對(duì)于常用的屬性的set,get操作進(jìn)行分裝。
/// <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>
/// 獲取對(duì)象的值
/// </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)
//聲明方法需要的參數(shù)
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);
}
}
在將代碼應(yīng)用到實(shí)例:
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); //表達(dá)式樹的操作 Property property = new Property(propertyInfo); property.SetValue(dog, "WLJ2"); String result2 = propertyInfo.GetValue(dog) as String; Console.WriteLine(result2);
發(fā)現(xiàn)其實(shí)現(xiàn)的目的與反射一致,但效率卻有明顯的提高。
以下測(cè)試以下他們兩之間的效率。測(cè)試代碼如下:
Student student = new Student();
PropertyInfo propertyInfo = student.GetType().GetProperty(nameof(student.Name));
Property ExpProperty = new Property(propertyInfo);
Int32 loopCount = 1000000;
CodeTimer.Initialize(); //測(cè)試環(huán)境初始化
//下面該方法個(gè)執(zhí)行1000000次
CodeTimer.Time("基礎(chǔ)反射", loopCount, () => {
propertyInfo.SetValue(student, "Fode",null);
});
CodeTimer.Time("lambda表達(dá)式樹", loopCount, () => {
ExpProperty.SetValue(student, "Fode");
});
CodeTimer.Time("直接賦值", loopCount, () => {
student.Name = "Fode";
});
Console.ReadKey();
其.Net4.0環(huán)境下運(yùn)行結(jié)果如下:

.Net Core環(huán)境下運(yùn)行結(jié)果:

從以上結(jié)果可以知道,迭代同樣的次數(shù)反射需要183ms,而用表達(dá)式只要34ms,直接賦值需要7ms,在效率上,使用表達(dá)式這種方法有顯著的提高,您可以看到使用此技術(shù)可以完全避免使用反射時(shí)的性能損失。反射之所以效率有點(diǎn)低主要取決于其加載的時(shí)候時(shí)在運(yùn)行期下,而表達(dá)式則在編譯期,下篇有空將會(huì)介紹用Emit技術(shù)優(yōu)化反射,會(huì)比表達(dá)式略快一點(diǎn)。
注:對(duì)于常用對(duì)象的屬性,最好將其緩存起來(lái),這樣效率會(huì)更高。。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- 實(shí)例講解Java基礎(chǔ)之反射
- ES6 如何改變JS內(nèi)置行為的代理與反射
- 實(shí)例講解Java中動(dòng)態(tài)代理和反射機(jī)制
- Java反射機(jī)制的精髓講解
- 詳解Golang利用反射reflect動(dòng)態(tài)調(diào)用方法
- Kotlin中的反射機(jī)制深入講解
- 基于Java反射的map自動(dòng)裝配JavaBean工具類設(shè)計(jì)示例代碼
- Java高級(jí)特性之反射機(jī)制實(shí)例詳解
- 使用反射機(jī)制控制Toast的顯示時(shí)間
- 通過反射注解批量插入數(shù)據(jù)到DB的實(shí)現(xiàn)方法
相關(guān)文章
C#無(wú)損高質(zhì)量壓縮圖片實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了C#無(wú)損高質(zhì)量壓縮圖片的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
C#中實(shí)現(xiàn)向數(shù)組中動(dòng)態(tài)添加元素
這篇文章主要介紹了C#中實(shí)現(xiàn)向數(shù)組中動(dòng)態(tài)添加元素方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
C# BeginInvoke實(shí)現(xiàn)異步編程方式
這篇文章主要介紹了C# BeginInvoke實(shí)現(xiàn)異步編程方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01

