一、介紹
- 在日常的開發過程中會出現,需要根據名稱來獲取某個未知對象的屬性,常用的方式是使用反射來完成此類效果,但是反射在性能方面要比較差,所以這個組件是使用反射+表達式樹來實現的獲取、設置屬性值,在性能上要比純反射速度要快1~2倍
- 內部實現原理:
1.根據傳入類型反射獲取是否有此屬性類型相等或支持隱式轉換或者裏式轉換
2.根據反射的屬性信息生成表達式樹,編譯成函數存入字典
3.下次再獲取就直接在字典中獲取之前編譯好的函數進行執行,這樣就可以獲得幾近於原生代碼的速度來獲取或設置屬性值
二、測試
1.測試代碼
class Program
{
static void Main(string[] args)
{
object o = new MyClass
{
Id = 113231,
My = new MyClass
{
Id = 123
}
};
Console.WriteLine("---------獲取屬性值---------");
long l = ObjectUtil<long>.GetPropertyValue(o, "Id");
decimal d = ObjectUtil<decimal>.GetPropertyValue(o, "Id");
BaseMyClass mc = ObjectUtil<BaseMyClass>.GetPropertyValue(o, "My");
IMyClass imc = ObjectUtil<IMyClass>.GetPropertyValue(o, "My");
Console.WriteLine(l);
Console.WriteLine(d);
Console.WriteLine(mc);
Console.WriteLine(imc);
Console.WriteLine("---------設置屬性值---------");
ObjectUtil<long>.SetPropertyValue(o, "Id", 999L);
ObjectUtil<int>.SetPropertyValue(o, "Id", 888);
ObjectUtil<MyClass>.SetPropertyValue(o, "My", new MyClass
{
Id = 777
});
Console.WriteLine("設置完成後o的值:{0}", o);
Console.WriteLine("---------讀取屬性值【性能測試100萬次】---------");
for (int x = 0; x < 4; x++)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
{
ObjectUtil<long>.GetPropertyValue(o, "Id");
}
sw.Stop();
Console.WriteLine("表達式樹耗時:{0}ms", sw.Elapsed.TotalMilliseconds);
sw.Restart();
for (int i = 0; i < 1000000; i++)
{
GetPropertyValue<long>(o, "Id");
}
sw.Stop();
Console.WriteLine("反射耗時:{0}ms", sw.Elapsed.TotalMilliseconds);
}
Console.WriteLine("---------設置屬性值【性能測試100萬次】---------");
for (int x = 0; x < 4; x++)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
{
ObjectUtil<long>.SetPropertyValue(o, "Id", i);
}
sw.Stop();
Console.WriteLine("表達式樹耗時:{0}ms", sw.Elapsed.TotalMilliseconds);
sw.Restart();
for (int i = 0; i < 1000000; i++)
{
SetPropertyValue<long>(o, "Id", i);
}
sw.Stop();
Console.WriteLine("反射耗時:{0}ms", sw.Elapsed.TotalMilliseconds);
}
}
private static void SetPropertyValue<T>(object o, string name, T value)
{
var type = o.GetType();
var info = type.GetProperty(name);
if (info == null)
throw new Exception(string.Format("類型“{0}”中沒有名稱爲“{1}”的公共屬性!", type, name));
if (info.PropertyType != typeof(T))
throw new Exception(string.Format("類型“{0}”中沒有名稱爲“{1}”且類型爲“{2}”的公共屬性!", type, name, typeof(T)));
if (!info.CanWrite)
throw new Exception(string.Format("類型“{0}”中沒有名稱爲“{1}”且類型爲“{2}”且可寫的公共屬性!", type, name, typeof(T)));
info.SetValue(o, value);
}
private static T GetPropertyValue<T>(object o, string name)
{
var type = o.GetType();
var info = type.GetProperty(name);
if (info == null)
throw new Exception(string.Format("類型“{0}”中沒有名稱爲“{1}”的公共屬性!", type, name));
if (info.PropertyType != typeof(T))
throw new Exception(string.Format("類型“{0}”中沒有名稱爲“{1}”且類型爲“{2}”的公共屬性!", type, name, typeof(T)));
if (!info.CanRead)
throw new Exception(string.Format("類型“{0}”中沒有名稱爲“{1}”且類型爲“{2}”且可讀的公共屬性!", type, name, typeof(T)));
return (T) info.GetValue(o);
}
public interface IMyClass
{
}
public abstract class MyClassAbstract
{
}
public class BaseMyClass : MyClassAbstract, IMyClass
{
}
public class MyClass : BaseMyClass
{
public long Id { get; set; }
public BaseMyClass My { get; set; }
public override string ToString()
{
return string.Format("Id = {0},My = [{1}]", Id, My);
}
}
}
2.輸出
---------獲取屬性值---------
113231
113231
Id = 123,My = []
Id = 123,My = []
---------設置屬性值---------
設置完成後o的值:Id = 888,My = [Id = 777,My = []]
---------讀取屬性值【性能測試100萬次】---------
表達式樹耗時:96.9933ms
反射耗時:244.9508ms
表達式樹耗時:94.9008ms
反射耗時:244.1905ms
表達式樹耗時:105.9869ms
反射耗時:244.3733ms
表達式樹耗時:94.7369ms
反射耗時:248.7527ms
---------設置屬性值【性能測試100萬次】---------
表達式樹耗時:106.949ms
反射耗時:304.0195ms
表達式樹耗時:98.9986ms
反射耗時:290.7388ms
表達式樹耗時:96.8362ms
反射耗時:294.8361ms
表達式樹耗時:96.2894ms
反射耗時:288.5725ms
請按任意鍵繼續. . .
三、函數介紹
方法名稱 |
介紹 |
T GetPropertyValue(object obj, string name) |
獲取指定名稱的公共屬性的值 |
void SetPropertyValue(object obj, string name, T value) |
設置指定名稱的公共屬性的值 |
bool TryGetPropertyValue(object obj, string name, out T value) |
嘗試獲取指定名稱的公共屬性的值 |
bool TrySetPropertyValue(object obj, string name, T value) |
嘗試設置指定名稱的公共屬性的值 |
四、實現代碼
1.獲取設置屬性類型
public static class ObjectUtil<T>
{
private static readonly ConcurrentDictionary<KeyInfo, Func<object, T>> ReadPropertyValueDictionary =
new ConcurrentDictionary<KeyInfo, Func<object, T>>();
private static readonly ConcurrentDictionary<KeyInfo, Action<object, T>> WritePropertyValueDictionary =
new ConcurrentDictionary<KeyInfo, Action<object, T>>();
public static T GetPropertyValue(object obj, string name)
{
if (obj == null) throw new Exception("傳入對象不能爲空!");
var type = obj.GetType();
var key = new KeyInfo(type, name);
Func<object, T> getValueAction;
if (!ReadPropertyValueDictionary.TryGetValue(key, out getValueAction))
{
string errorMessage;
if (CreateReadPropertyFunc(type, name, out getValueAction, out errorMessage))
{
getValueAction = ReadPropertyValueDictionary.GetOrAdd(key, getValueAction);
}
else
{
throw new Exception(errorMessage);
}
}
return getValueAction.Invoke(obj);
}
public static bool TryGetPropertyValue(object obj, string name, out T value)
{
if (obj == null)
{
value = default(T);
return false;
}
var type = obj.GetType();
var key = new KeyInfo(type, name);
Func<object, T> getValueAction;
if (!ReadPropertyValueDictionary.TryGetValue(key, out getValueAction))
{
string errorMessage;
if (CreateReadPropertyFunc(type, name, out getValueAction, out errorMessage))
{
getValueAction = ReadPropertyValueDictionary.GetOrAdd(key, getValueAction);
}
else
{
value = default(T);
return false;
}
}
value = getValueAction.Invoke(obj);
return true;
}
public static void SetPropertyValue(object obj, string name, T value)
{
if (obj == null) throw new Exception("傳入對象不能爲空!");
var type = obj.GetType();
var key = new KeyInfo(type, name);
Action<object, T> setValueAction;
if (!WritePropertyValueDictionary.TryGetValue(key, out setValueAction))
{
string errorMessage;
if (CreateWritePropertyFunc(type, name, out setValueAction, out errorMessage))
{
setValueAction = WritePropertyValueDictionary.GetOrAdd(key, setValueAction);
}
else
{
throw new Exception(errorMessage);
}
}
setValueAction.Invoke(obj, value);
}
public static bool TrySetPropertyValue(object obj, string name, T value)
{
if (obj == null) return false;
var type = obj.GetType();
var key = new KeyInfo(type, name);
Action<object, T> setValueAction;
if (!WritePropertyValueDictionary.TryGetValue(key, out setValueAction))
{
string errorMessage;
if (CreateWritePropertyFunc(type, name, out setValueAction, out errorMessage))
{
setValueAction = WritePropertyValueDictionary.GetOrAdd(key, setValueAction);
}
else
{
return false;
}
}
setValueAction.Invoke(obj, value);
return true;
}
private static bool CreateWritePropertyFunc(Type type, string name, out Action<object, T> setValueAction,
out string errorMessage)
{
var info = type.GetProperty(name);
if (info == null)
{
errorMessage = string.Format("類型“{0}”中沒有名稱爲“{1}”的公共屬性!", type, name);
setValueAction = null;
return false;
}
if (info.PropertyType != typeof(T)
&& !(info.PropertyType.IsValueType && typeof(T).IsValueType &&
ImplicitConversionUtil.ImplicitConversion(typeof(T), info.PropertyType))
&& !info.PropertyType.IsAssignableFrom(typeof(T)))
{
errorMessage = string.Format("類型“{0}”中沒有名稱爲“{1}”且類型可設置爲“{2}”的公共屬性!", type, name, typeof(T));
setValueAction = null;
return false;
}
if (!info.CanWrite)
{
errorMessage = string.Format("類型“{0}”中沒有名稱爲“{1}”且類型爲“{2}”且可寫的公共屬性!", type, name, typeof(T));
setValueAction = null;
return false;
}
var oExpression = Expression.Parameter(typeof(object));
var vExpression = Expression.Parameter(typeof(T));
var convertExpression = Expression.Convert(oExpression, type);
var infoExpression = Expression.Property(convertExpression, info);
var assign = Expression.Assign(infoExpression,
info.PropertyType == typeof(T)
? (Expression) vExpression
: Expression.Convert(vExpression, info.PropertyType)
);
var lambda = Expression.Lambda<Action<object, T>>(assign, oExpression, vExpression);
setValueAction = lambda.Compile();
errorMessage = null;
return true;
}
private static bool CreateReadPropertyFunc(Type type, string name, out Func<object, T> getValueFunc,
out string errorMessage)
{
var info = type.GetProperty(name);
if (info == null)
{
errorMessage = string.Format("類型“{0}”中沒有名稱爲“{1}”的公共屬性!", type, name);
getValueFunc = null;
return false;
}
if (info.PropertyType != typeof(T)
&& !(info.PropertyType.IsValueType && typeof(T).IsValueType &&
ImplicitConversionUtil.ImplicitConversion(info.PropertyType, typeof(T)))
&& !typeof(T).IsAssignableFrom(info.PropertyType))
{
errorMessage = string.Format("類型“{0}”中沒有名稱爲“{1}”且類型可轉換爲“{2}”的公共屬性!", type, name, typeof(T));
getValueFunc = null;
return false;
}
if (!info.CanRead)
{
errorMessage = string.Format("類型“{0}”中沒有名稱爲“{1}”且類型爲“{2}”且可讀的公共屬性!", type, name, typeof(T));
getValueFunc = null;
return false;
}
var oExpression = Expression.Parameter(typeof(object));
var convertExpression = Expression.Convert(oExpression, type);
Expression infoExpression = Expression.Property(convertExpression, info);
var lambda = Expression.Lambda<Func<object, T>>(
info.PropertyType == typeof(T)
? infoExpression
: Expression.Convert(infoExpression, typeof(T))
, oExpression);
getValueFunc = lambda.Compile();
errorMessage = null;
return true;
}
private struct KeyInfo : IEquatable<KeyInfo>
{
public KeyInfo(Type type, string name)
{
Type = type;
Name = name;
}
public Type Type { get; private set; }
public string Name { get; private set; }
public bool Equals(KeyInfo other)
{
return Equals(Type, other.Type) && Name == other.Name;
}
public override bool Equals(object obj)
{
if (!(obj is KeyInfo)) return false;
return Equals((KeyInfo) obj);
}
public override int GetHashCode()
{
unchecked
{
return ((Type != null ? Type.GetHashCode() : 0) * 397) ^ (Name != null ? Name.GetHashCode() : 0);
}
}
}
}
2.隱式轉換幫助類
public class ImplicitConversionUtil
{
private static readonly Dictionary<Type, HashSet<Type>> ImplicitConversionDictionary =
new Dictionary<Type, HashSet<Type>>(10);
static ImplicitConversionUtil()
{
ImplicitConversionDictionary[typeof(sbyte)] = new HashSet<Type>(new[]
{
typeof(short),
typeof(int),
typeof(long),
typeof(float),
typeof(double),
typeof(decimal),
});
ImplicitConversionDictionary[typeof(byte)] = new HashSet<Type>(new[]
{
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
});
ImplicitConversionDictionary[typeof(short)] = new HashSet<Type>(new[]
{
typeof(int),
typeof(long),
typeof(float),
typeof(double),
typeof(decimal),
});
ImplicitConversionDictionary[typeof(ushort)] = new HashSet<Type>(new[]
{
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
});
ImplicitConversionDictionary[typeof(int)] = new HashSet<Type>(new[]
{
typeof(long),
typeof(float),
typeof(double),
typeof(decimal),
});
ImplicitConversionDictionary[typeof(uint)] = new HashSet<Type>(new[]
{
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
});
ImplicitConversionDictionary[typeof(long)] = new HashSet<Type>(new[]
{
typeof(float),
typeof(double),
typeof(decimal),
});
ImplicitConversionDictionary[typeof(char)] = new HashSet<Type>(new[]
{
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
});
ImplicitConversionDictionary[typeof(float)] = new HashSet<Type>(new[]
{
typeof(double),
});
ImplicitConversionDictionary[typeof(ulong)] = new HashSet<Type>(new[]
{
typeof(float),
typeof(double),
typeof(decimal),
});
}
public static bool ImplicitConversion(Type fromType, Type arriveType)
{
HashSet<Type> set;
return ImplicitConversionDictionary.TryGetValue(fromType, out set) && set.Contains(arriveType);
}
}