通過表達式樹實現,根據名稱獲取或設置對象屬性值(性能相較於反射而言要快1~2倍)

一、介紹

  • 在日常的開發過程中會出現,需要根據名稱來獲取某個未知對象的屬性,常用的方式是使用反射來完成此類效果,但是反射在性能方面要比較差,所以這個組件是使用反射+表達式樹來實現的獲取、設置屬性值,在性能上要比純反射速度要快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);
        }
    }

    /// <summary>
    /// 反射設置屬性值
    /// </summary>
    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);
    }

    /// <summary>
    /// 反射獲取屬性值
    /// </summary>
    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.獲取設置屬性類型
/// <summary>
/// 對象操作類
/// </summary>
/// <typeparam name="T">獲取或者設置屬性的類型</typeparam>
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>>();

    /// <summary>
    /// 獲取指定名稱的公共屬性的值
    /// </summary>
    /// <param name="obj">實例對象</param>
    /// <param name="name">屬性名稱</param>
    /// <returns>值</returns>
    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);
    }

    /// <summary>
    /// 嘗試獲取指定名稱的公共屬性的值
    /// </summary>
    /// <param name="obj">實例對象</param>
    /// <param name="name">屬性名稱</param>
    /// <param name="value">屬性值</param>
    /// <returns>是否獲取成功</returns>
    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;
    }

    /// <summary>
    /// 設置指定名稱的公共屬性的值
    /// </summary>
    /// <param name="obj">實例對象</param>
    /// <param name="name">屬性名稱</param>
    /// <param name="value">設置的值</param>
    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);
    }

    /// <summary>
    /// 嘗試設置指定名稱的公共屬性的值
    /// </summary>
    /// <param name="obj">實例對象</param>
    /// <param name="name">屬性名稱</param>
    /// <param name="value">設置的值</param>
    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;
    }

    /// <summary>
    /// 創建寫入函數
    /// </summary>
    /// <param name="type">類型</param>
    /// <param name="name">寫入的字段</param>
    /// <param name="setValueAction">寫入函數</param>
    /// <param name="errorMessage">異常信息</param>
    /// <returns>是否創建寫入函數成功</returns>
    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;
    }

    /// <summary>
    /// 創建讀取函數
    /// </summary>
    /// <param name="type">類型</param>
    /// <param name="name">讀取的字段</param>
    /// <param name="getValueFunc">讀取函數</param>
    /// <param name="errorMessage">異常信息</param>
    /// <returns>是否創建寫入函數成功</returns>
    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;
    }

    /// <summary>
    /// [Type,Name]作爲Key的數據結構
    /// </summary>
    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.隱式轉換幫助類
/// <summary>
/// 隱式轉換幫助類
/// </summary>
public class ImplicitConversionUtil
{
    /// <summary>
    /// 隱式轉換表
    /// </summary>
    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),
        });
    }

    /// <summary>
    /// 判斷是否可用進行隱式轉換
    /// </summary>
    /// <param name="fromType">從什麼類型</param>
    /// <param name="arriveType">到什麼類型</param>
    /// <returns>是否可用進行隱式轉換</returns>
    public static bool ImplicitConversion(Type fromType, Type arriveType)
    {
        HashSet<Type> set;
        return ImplicitConversionDictionary.TryGetValue(fromType, out set) && set.Contains(arriveType);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章