Unity C# 反射效能優化

冰封百度發表於2022-06-14

原文地址:https://segmentfault.com/a/11...

本篇內容主要講如何優化獲取(設定)物件欄位(屬性)的反射效能。

先上結論:

1.反射效能很差,比直接呼叫慢成百上千倍。
2.反射效能優化的思路是繞開反射。
3.反射效能優化方式有:字典快取(Dictionary Cache)、委託(Delegate.CreateDelegate)、Expression.Lamda、IL.Emit、指標(IntPtr) 等各種方式。
4.效能排名:直接呼叫 > (IL.Emit == Expression) > 委託 > 指標 > 反射
5.通用性排名:反射 > (委託 == 指標) > (IL.Emit == Expression) > 直接呼叫
6.綜合排名:(委託 == 指標) > (IL.Emit == Expression) > 反射 > 直接呼叫

IL.Emit本是效能最優方案,但可惜IL2CPP模式不支援JIT 所有動態生成程式碼的功能都不好用 故IL.Emit無法使用、Expression.Lamda也是同樣的原因不可用。
退而求其次選用效能和通用性都不錯的委託,由於委託只能優化方法無法用於欄位,所以又加上了指標用於操作欄位。


效能測試:

以設定物件的屬性Name為測試條件(obj.Name = "A")
SetValue方法都調整為通用的SetValue(object target, object value)狀態。
除了反射,所有呼叫方法都建立委託後再呼叫,省略查詢委託快取的步驟。
在Release模式下,迴圈多次呼叫,檢視耗時。
如果直接呼叫耗時約為:1ms,其他方式耗時約為:

直接呼叫IL.EmitExpression委託(Delegate)反射(MethodInfo)
1ms1.3ms1.3ms2.1ms91.7ms

指標只操作欄位, 不參與該項測試

如果測試條件改為每次獲取或設定值都需要通過Dictionary從快取中查詢欄位所對應的委託方法後呼叫,那麼時間將大大增加,大部分時間都浪費在了字典查詢上,但這也更貼近我們正常使用的情況。

直接呼叫=IL.EmitExpression委託(Delegate)反射(MethodInfo)
1ms8.8ms8.8ms9.7ms192.5ms

設定string欄位測試:

直接呼叫=IL.EmitExpression指標反射(MethodInfo)
1ms8ms8ms9ms122ms

委託只操作屬性, 不參與該項測試

從測試結果可以看出
直接呼叫 > IL.Emit(Expression與IL一致) > 委託(指標與委託互補)> 反射

基本上只要能繞開反射,效能都會有明顯的提升。


IL.Emit

優點:效能非常高。
缺點:不支援IL2CPP。

IL.Emit通用工具類:

用法:

var getName = ILUtil.CreateGetValue(type, "Name");
var setName = ILUtil.CreateSetValue(type, "Name");
setName(obj, "A");
string name = (string)getName(obj);

程式碼:

#region Intro
// Purpose: Reflection Optimize Performance
// Author: ZhangYu
// LastModifiedDate: 2022-06-11
#endregion

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;

/// <summary> IL.Emit工具 </summary>
public static class ILUtil {

    private static Type[] NoTypes = new Type[] { };

    /// <summary> IL動態新增建立新物件方法 </summary>
    public static Func<object> CreateCreateInstance(Type classType) {
        DynamicMethod method = new DynamicMethod(string.Empty, typeof(object), null, classType);
        if (classType.IsValueType) {
            // 例項化值型別
            ILGenerator il = method.GetILGenerator(32);
            var v0 = il.DeclareLocal(classType);
            il.Emit(OpCodes.Ldloca_S, v0);
            il.Emit(OpCodes.Initobj, classType);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Box, classType);
            il.Emit(OpCodes.Ret);
        } else {
            // 例項化引用型別
            ConstructorInfo ctor = classType.GetConstructor(NoTypes);
            ILGenerator il = method.GetILGenerator(16);
            il.Emit(OpCodes.Newobj, ctor);
            il.Emit(OpCodes.Ret);
        }
        return method.CreateDelegate(typeof(Func<object>)) as Func<object>;
    }

    /// <summary> IL動態新增建立新陣列方法 </summary>
    public static Func<int, Array> CreateCreateArray(Type classType, int length) {
        DynamicMethod method = new DynamicMethod(string.Empty, typeof(Array), new Type[] { typeof(int) }, typeof(Array));
        ILGenerator il = method.GetILGenerator(16);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Newarr, classType.GetElementType());
        il.Emit(OpCodes.Ret);
        return method.CreateDelegate(typeof(Func<int, Array>)) as Func<int, Array>;
    }

    private static Dictionary<Type, Func<object>> createInstanceCache = new Dictionary<Type, Func<object>>();
    private static Dictionary<Type, Func<int, Array>> createArrayCache = new Dictionary<Type, Func<int, Array>>();

    /// <summary> IL動態建立新的例項 </summary>
    /// <param name="classType">型別</param>
    public static object CreateInstance(Type classType) {
        Func<object> createMethod = null;
        if (!createInstanceCache.TryGetValue(classType, out createMethod)) {
            createMethod = CreateCreateInstance(classType);
            createInstanceCache.Add(classType, createMethod);
        }
        return createMethod();
    }


    /// <summary> IL動態建立新陣列 </summary>
    public static Array CreateArray(Type classType, int length) {
        Func<int, Array> createMethod = null;
        if (!createArrayCache.TryGetValue(classType, out createMethod)) {
            createMethod = CreateCreateArray(classType, length);
            createArrayCache.Add(classType, createMethod);
        }
        return createMethod(length);
    }

    /// <summary> IL.Emit動態建立獲取欄位值的方法 </summary>
    private static Func<object, object> CreateGetField(Type classType, string fieldName) {
        FieldInfo field = classType.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public);
        DynamicMethod method = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object) }, classType);
        ILGenerator il = method.GetILGenerator(16); // 預設大小64位元組
        il.Emit(OpCodes.Ldarg_0);
        if (classType.IsValueType) il.Emit(OpCodes.Unbox_Any, classType);
        il.Emit(OpCodes.Ldfld, field);
        if (field.FieldType.IsValueType) il.Emit(OpCodes.Box, field.FieldType);
        il.Emit(OpCodes.Ret);
        return method.CreateDelegate(typeof(Func<object, object>)) as Func<object, object>;
    }

    /// <summary> IL.Emit動態建立設定欄位方法 </summary>
    private static Action<object, object> CreateSetField(Type classType, string fieldName) {
        FieldInfo field = classType.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public);
        DynamicMethod method = new DynamicMethod(string.Empty, null, new Type[] { typeof(object), typeof(object) }, classType);
        ILGenerator il = method.GetILGenerator(32); // 預設大小64位元組
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(classType.IsValueType ? OpCodes.Unbox : OpCodes.Castclass, classType);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(field.FieldType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, field.FieldType);
        il.Emit(OpCodes.Stfld, field);
        il.Emit(OpCodes.Ret);
        return method.CreateDelegate(typeof(Action<object, object>)) as Action<object, object>;
    }

    /// <summary> IL.Emit動態建立獲取屬性值的方法 </summary>
    private static Func<object, object> CreateGetProperty(Type classType, string propertyName) {
        PropertyInfo property = classType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
        DynamicMethod method = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object) }, classType);
        ILGenerator il = method.GetILGenerator(32);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(classType.IsValueType ? OpCodes.Unbox : OpCodes.Castclass, classType);
        il.Emit(OpCodes.Call, property.GetGetMethod());
        if (property.PropertyType.IsValueType) il.Emit(OpCodes.Box, property.PropertyType);
        il.Emit(OpCodes.Ret);
        return method.CreateDelegate(typeof(Func<object, object>)) as Func<object, object>;
    }

    /// <summary> IL.Emit動態建立設定屬性值的方法 </summary>
    private static Action<object, object> CreateSetProperty(Type classType, string propertyName) {
        PropertyInfo property = classType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
        MethodInfo methodInfo = property.GetSetMethod();
        ParameterInfo parameter = methodInfo.GetParameters()[0];
        DynamicMethod method = new DynamicMethod(string.Empty, null, new Type[] { typeof(object), typeof(object) }, classType);
        ILGenerator il = method.GetILGenerator(32);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(classType.IsValueType ? OpCodes.Unbox : OpCodes.Castclass, classType);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(parameter.ParameterType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, parameter.ParameterType);
        il.Emit(OpCodes.Call, methodInfo);
        il.Emit(OpCodes.Ret);
        return method.CreateDelegate(typeof(Action<object, object>)) as Action<object, object>;
    }

    /// <summary> IL.Emit動態建立獲取欄位或屬性值的方法 </summary>
    /// <param name="classType">物件型別</param>
    /// <param name="fieldName">欄位(或屬性)名稱</param>
    public static Func<object, object> CreateGetValue(Type classType, string fieldName) {
        MemberInfo[] members = classType.GetMember(fieldName, BindingFlags.Instance | BindingFlags.Public);
        if (members.Length == 0) {
            string error = "Type [{0}] don't contains member [{1}]";
            throw new Exception(string.Format(error, classType, fieldName));
        }
        Func<object, object> getValue = null;
        switch (members[0].MemberType) {
            case MemberTypes.Field:
                getValue = CreateGetField(classType, fieldName);
                break;
            case MemberTypes.Property:
                getValue = CreateGetProperty(classType, fieldName);
                break;
            default:
                break;
        }
        return getValue;
    }

    /// <summary> IL.Emit動態建立設定欄位值(或屬性)值的方法 </summary>
    /// <param name="classType">物件型別</param>
    /// <param name="fieldName">欄位(或屬性)名稱</param>
    public static Action<object, object> CreateSetValue(Type classType, string fieldName) {
        MemberInfo[] members = classType.GetMember(fieldName, BindingFlags.Instance | BindingFlags.Public);
        if (members.Length == 0) {
            string error = "Type [{0}] does not contain field [{1}]";
            throw new Exception(string.Format(error, classType, fieldName));
        }
        Action<object, object> setValue = null;
        switch (members[0].MemberType) {
            case MemberTypes.Field:
                setValue = CreateSetField(classType, fieldName);
                break;
            case MemberTypes.Property:
                setValue = CreateSetProperty(classType, fieldName);
                break;
            default:
                break;
        }
        return setValue;
    }

    // Emit Getter 方法快取字典
    private static Dictionary<Type, Dictionary<string, Func<object, object>>> getValueCache = new Dictionary<Type, Dictionary<string, Func<object, object>>>();
    // Emit Setter 方法快取字典
    private static Dictionary<Type, Dictionary<string, Action<object, object>>> setValueCache = new Dictionary<Type, Dictionary<string, Action<object, object>>>();

    /// <summary> IL 獲取物件成員的值(欄位或屬性) </summary>
    public static object GetValue(object obj, string fieldName) {
        // 查詢一級快取
        Type classType = obj.GetType();
        Dictionary<string, Func<object, object>> cache = null;
        if (!getValueCache.TryGetValue(classType, out cache)) {
            cache = new Dictionary<string, Func<object, object>>();
            getValueCache.Add(classType, cache);
        }

        // 查詢二級快取
        Func<object, object> getValue = null;
        if (!cache.TryGetValue(fieldName, out getValue)) {
            getValue = CreateGetValue(classType, fieldName);
            cache.Add(fieldName, getValue);
        }
        return getValue(obj);
    }

    /// <summary> IL 設定物件成員的值(欄位或屬性) </summary>
    public static void SetValue(object obj, string fieldName, object value) {
        // 查詢一級快取
        Type classType = obj.GetType();
        Dictionary<string, Action<object, object>> cache = null;
        if (!setValueCache.TryGetValue(classType, out cache)) {
            cache = new Dictionary<string, Action<object, object>>();
            setValueCache.Add(classType, cache);
        }

        // 查詢二級快取
        Action<object, object> setValue = null;
        if (!cache.TryGetValue(fieldName, out setValue)) {
            setValue = CreateSetValue(classType, fieldName);
            cache.Add(fieldName, setValue);
        }
        setValue(obj, value);
    }

    /// <summary> 清理已快取IL方法 </summary>
    public static void ClearCache() {
        createInstanceCache.Clear();
        createArrayCache.Clear();
        getValueCache.Clear();
        setValueCache.Clear();
    }

}

Expression.Lamda

優點:效能非常高。
缺點:不支援IL2CPP。
和IL.Emit一樣。

Expression通用工具類:

用法:

var wrapper = ExpressionUtil.GetPropertyWrapper(type, "Name");
wrapper.SetValue(obj, "A");
string name = (string)wrapper.GetValue(obj);

程式碼:

using System;
using System.Reflection;
using System.Linq.Expressions;
using System.Collections.Generic;

/// <summary> 
/// <para>Expression.Lamda 反射加速工具</para>
/// <para>ZhangYu 2022-06-11</para>
/// </summary>
public static class ExpressionUtil {

    public class PropertyWrapper {

        public Func<object, object> GetValue { get; private set; }
        public Action<object, object> SetValue { get; private set; }

        public PropertyWrapper(Func<object, object> getValue, Action<object, object> setValue) {
            GetValue = getValue;
            SetValue = setValue;
        }

    }

    public static Func<object, object> CreateGetProperty(Type type, string propertyName) {
        var objectObj = Expression.Parameter(typeof(object), "obj");
        var classObj = Expression.Convert(objectObj, type);
        var classFunc = Expression.Property(classObj, propertyName);
        var objectFunc = Expression.Convert(classFunc, typeof(object));
        Func<object, object> getValue = Expression.Lambda<Func<object, object>>(objectFunc, objectObj).Compile();
        return getValue;
    }

    public static Action<object, object> CreateSetProperty(Type type, string propertyName) {
        var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
        var objectObj = Expression.Parameter(typeof(object), "obj");
        var objectValue = Expression.Parameter(typeof(object), "value");
        var classObj = Expression.Convert(objectObj, type);
        var classValue = Expression.Convert(objectValue, property.PropertyType);
        var classFunc = Expression.Call(classObj, property.GetSetMethod(), classValue);
        var setProperty = Expression.Lambda<Action<object, object>>(classFunc, objectObj, objectValue).Compile();
        return setProperty;
    }

    private static Dictionary<Type, Dictionary<string, PropertyWrapper>> cache = new Dictionary<Type, Dictionary<string, PropertyWrapper>>();

    public static PropertyWrapper GetPropertyWrapper(Type type, string propertyName) {
        // 查詢一級快取
        Dictionary<string, PropertyWrapper> wrapperDic = null;
        if (!cache.TryGetValue(type, out wrapperDic)) {
            wrapperDic = new Dictionary<string, PropertyWrapper>();
            cache.Add(type, wrapperDic);
        }

        // 查詢二級快取
        PropertyWrapper wrapper = null;
        if (!wrapperDic.TryGetValue(propertyName, out wrapper)) {
            var getValue = CreateGetProperty(type, propertyName);
            var setValue = CreateSetProperty(type, propertyName);
            wrapper = new PropertyWrapper(getValue, setValue);
            wrapperDic.Add(propertyName, wrapper);
        }
        return wrapper;
    }

    public static void ClearCache() {
        cache.Clear();
    }

}

委託(Delegate)

優點:效能高,通用性強(IL2CPP下也可使用)。
缺點:只能操作方法,無法直接操作欄位

委託通用工具類:
用法:

var wrapper = DelegateUtil.GetPropertyWrapper(type, "Name");
wrapper.SetValue(obj, "A");
string name = (string)wrapper.GetValue(obj);

程式碼:

using System;
using System.Reflection;
using System.Collections.Generic;

public interface IPropertyWrapper {

    object GetValue(object obj);
    void SetValue(object obj, object value);

}

public class PropertyWrapper<T, V> : IPropertyWrapper {

    private Func<T, V> getter;
    private Action<T, V> setter;

    public PropertyWrapper(PropertyInfo property) {
        getter = Delegate.CreateDelegate(typeof(Func<T, V>), property.GetGetMethod()) as Func<T, V>;
        setter = Delegate.CreateDelegate(typeof(Action<T, V>), property.GetSetMethod()) as Action<T, V>;
    }

    public V GetValue(T obj) {
        return getter(obj);
    }

    public void SetValue(T obj, V value) {
        setter(obj, value);
    }

    public object GetValue(object obj) {
        return GetValue((T)obj);
    }

    public void SetValue(object obj, object value) {
        SetValue((T)obj, (V)value);
    }

}

/// <summary> 委託工具類 ZhangYu 2022-06-10 </summary>
public static class DelegateUtil {

    private static Dictionary<Type, Dictionary<string, IPropertyWrapper>> cache = new Dictionary<Type, Dictionary<string, IPropertyWrapper>>();

    /// <summary> 獲取屬性包裝器 </summary>
    public static IPropertyWrapper GetPropertyWrapper(Type type, string propertyName) {
        if (string.IsNullOrEmpty(propertyName)) throw new Exception("propertyName is null");
        // 查詢型別
        Dictionary<string, IPropertyWrapper> proCache = null;
        if (!cache.TryGetValue(type, out proCache)) {
            proCache = new Dictionary<string, IPropertyWrapper>();
            cache.Add(type, proCache);
        }

        // 查詢屬性
        IPropertyWrapper wrapper = null;
        if (!proCache.TryGetValue(propertyName, out wrapper)) {
            PropertyInfo property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
            if (property == null) throw new Exception(type.Name + " type no property:" + propertyName);
            Type wrapperType = typeof(PropertyWrapper<,>).MakeGenericType(type, property.PropertyType);
            wrapper = Activator.CreateInstance(wrapperType, property) as IPropertyWrapper;
            proCache.Add(propertyName, wrapper);
        }
        return wrapper;
    }

    /// <summary> 清理快取 </summary>
    public static void ClearCache() {
        cache.Clear();
    }

}

指標

優點:效能高,通用性好(IL2CPP下也可使用)。
缺點:暫時不知道怎樣用指標操作屬性(方法)。

指標通用工具類:

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.InteropServices;
#if !ENABLE_MONO
using Unity.Collections.LowLevel.Unsafe;
#endif

/// <summary> 指標工具 ZhangYu 2022-06-10 </summary>
public static class PointerUtil {

    public class FieldWrapper {

        public string fieldName;
        public Type fieldType;
        public bool isValueType;
        public TypeCode typeCode;
        public int offset;
        private static bool is64Bit = Environment.Is64BitProcess;

        #region Instance Function
        /// <summary> 獲取欄位值 </summary>
        public unsafe object GetValue(object obj) {
            // 檢查引數
            CheckObjAndFieldName(obj, fieldName);

            // 查詢物件資訊
            Type type = obj.GetType();

            // 獲取值
            IntPtr ptr = GetPointer(obj) + offset;
            object value = null;
            switch (typeCode) {
                case TypeCode.Boolean:
                    value = *(bool*)ptr;
                    break;
                case TypeCode.Char:
                    value = *(char*)ptr;
                    break;
                case TypeCode.SByte:
                    value = *(sbyte*)ptr;
                    break;
                case TypeCode.Byte:
                    value = *(byte*)ptr;
                    break;
                case TypeCode.Int16:
                    value = *(short*)ptr;
                    break;
                case TypeCode.UInt16:
                    value = *(ushort*)ptr;
                    break;
                case TypeCode.Int32:
                    value = *(int*)ptr;
                    break;
                case TypeCode.UInt32:
                    value = *(uint*)ptr;
                    break;
                case TypeCode.Int64:
                    value = *(long*)ptr;
                    break;
                case TypeCode.UInt64:
                    value = *(ulong*)ptr;
                    break;
                case TypeCode.Single:
                    value = *(float*)ptr;
                    break;
                case TypeCode.Double:
                    value = *(double*)ptr;
                    break;
                case TypeCode.Decimal:
                    value = *(decimal*)ptr;
                    break;
                case TypeCode.DateTime:
                    value = *(DateTime*)ptr;
                    break;
                case TypeCode.DBNull:
                case TypeCode.String:
                case TypeCode.Object:
                    if (isValueType) {
                        value = GetStruct(ptr, fieldType);
                    } else {
                        value = GetObject(ptr);
                    }
                    break;
                default:
                    break;
            }
            return value;
        }

        /// <summary> 給欄位賦值 </summary>
        public unsafe void SetValue(object obj, object value) {
            // 檢查引數
            CheckObjAndFieldName(obj, fieldName);

            // 查詢物件資訊
            Type type = obj.GetType();

            // 獲取值
            IntPtr ptr = GetPointer(obj) + offset;
            switch (typeCode) {
                case TypeCode.Boolean:
                    *(bool*)ptr = (bool)value;
                    break;
                case TypeCode.Char:
                    *(char*)ptr = (char)value;
                    break;
                case TypeCode.SByte:
                    *(sbyte*)ptr = (sbyte)value;
                    break;
                case TypeCode.Byte:
                    *(byte*)ptr = (byte)value;
                    break;
                case TypeCode.Int16:
                    *(short*)ptr = (short)value;
                    break;
                case TypeCode.UInt16:
                    *(ushort*)ptr = (ushort)value;
                    break;
                case TypeCode.Int32:
                    *(int*)ptr = (int)value;
                    break;
                case TypeCode.UInt32:
                    *(uint*)ptr = (uint)value;
                    break;
                case TypeCode.Int64:
                    *(long*)ptr = (long)value;
                    break;
                case TypeCode.UInt64:
                    *(ulong*)ptr = (ulong)value;
                    break;
                case TypeCode.Single:
                    *(float*)ptr = (float)value;
                    break;
                case TypeCode.Double:
                    *(double*)ptr = (double)value;
                    break;
                case TypeCode.Decimal:
                    *(decimal*)ptr = (decimal)value;
                    break;
                case TypeCode.DateTime:
                    *(DateTime*)ptr = (DateTime)value;
                    break;
                case TypeCode.DBNull:
                case TypeCode.String:
                case TypeCode.Object:
                    if (isValueType) {
                        SetStruct(ptr, value);
                    } else {
                        SetObject(ptr, value);
                    }
                    break;
                default:
                    break;
            }

        }
        #endregion

        #region Static Fucntion
        /// <summary> 獲取物件地址 </summary>
        private unsafe static IntPtr GetPointer(object obj) {
        #if ENABLE_MONO
            TypedReference tr = __makeref(obj);
            return *(IntPtr*)*((IntPtr*)&tr + 1);
        #else
            ulong gcHandle;
            IntPtr ptr = (IntPtr)UnsafeUtility.PinGCObjectAndGetAddress(obj, out gcHandle);
            UnsafeUtility.ReleaseGCObject(gcHandle);
            return ptr;
        #endif
        }

        /// <summary> 將物件的地址設定到目標地址,有型別判定和引用計數,推薦在堆上操作 </summary>
        private unsafe static void SetObject(IntPtr ptr, object value) {
        #if ENABLE_MONO
            object tmp = "";
            TypedReference tr = __makeref(tmp);
            if (is64Bit) {
                long* p = (long*)&tr + 1;
                *p = (long)ptr;
                __refvalue(tr, object) = value;
            } else {
                int* p = (int*)&tr + 1;
                *p = (int)ptr;
                __refvalue(tr, object) = value;
            }
        #else
            UnsafeUtility.CopyObjectAddressToPtr(value, (void*)ptr);
        #endif
        }

        /// <summary> 設定Struct </summary>
        private static void SetStruct(IntPtr ptr, object value) {
            Marshal.StructureToPtr(value, ptr, true);
        }

        /// <summary> 獲取物件 </summary>
        private unsafe static object GetObject(IntPtr ptr) {
        #if ENABLE_MONO
            object tmp = "";
            TypedReference tr = __makeref(tmp);
            if (is64Bit) {
                long* p = (long*)&tr + 1;
                *p = (long)ptr;
                return __refvalue(tr, object);
            } else {
                int* p = (int*)&tr + 1;
                *p = (int)ptr;
                return __refvalue(tr, object);
            }
        #else
            return UnsafeUtility.ReadArrayElement<object>((void*)ptr, 0);
        #endif
        }

        /// <summary> 獲取Struct </summary>
        private static object GetStruct(IntPtr ptr, Type type) {
            return Marshal.PtrToStructure(ptr, type);
        }

        /// <summary> 檢查引數 </summary>
        private static void CheckObjAndFieldName(object obj, string fieldName) {
            if (obj == null) throw new Exception("obj can't be null");
            if (string.IsNullOrEmpty(fieldName)) throw new Exception("FieldName can't be null or empty");
        }

        /// <summary> 獲取欄位地址偏移量 </summary>
        public unsafe static int GetFieldOffset(FieldInfo field) {
            return *(short*)(field.FieldHandle.Value + 24);
        }
        #endregion

    }

    /// <summary> 快取 </summary>
    private static Dictionary<Type, Dictionary<string, FieldWrapper>> cache = new Dictionary<Type, Dictionary<string, FieldWrapper>>();

    public static FieldWrapper GetFieldWrapper(Type type, string fieldName) {
        // 查詢一級快取
        Dictionary<string, FieldWrapper> wrapperDic = null;
        if (!cache.TryGetValue(type, out wrapperDic)) {
            wrapperDic = new Dictionary<string, FieldWrapper>();
            cache.Add(type, wrapperDic);
        }

        // 查詢二級快取
        FieldWrapper wrapper = null;
        if (!wrapperDic.TryGetValue(fieldName, out wrapper)) {
            FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
            if (field == null) throw new Exception(type.Name + " field is null:" + fieldName);
            wrapper = new FieldWrapper();
            wrapper.fieldName = fieldName;
            wrapper.fieldType = field.FieldType;
            wrapper.isValueType = field.FieldType.IsValueType;
            wrapper.typeCode = Type.GetTypeCode(field.FieldType);
            wrapper.offset = FieldWrapper.GetFieldOffset(field);
            wrapperDic.Add(fieldName, wrapper);
        }
        return wrapper;
    }

    /// <summary> 清理快取資料 </summary>
    public static void ClearCache() {
        cache.Clear();
    }

}

優化方案選擇:

優化方案這麼多到底用哪個? 有最好的嗎? (可以參考以下建議)

1.首選IL.Emit方案,效能很好,唯一的缺點是不相容IL2CPP模式。

2.次選委託(Delegate.CreateDelegate)方案,在IL2CPP模式下用於替代IL.Emit,由於委託只能操作方法,不能操作欄位,所以儘量寫成{get; set;}的形式,如果有欄位需要操作,那麼再加上指標方案(IntPtr)。

終極優化手段:

如果以上手段你都嘗試過了,依然不滿意,作為一個成年人你想魚和熊掌二者兼得,效能要最高的,通用性也要最好的,還要方便易用,你可以嘗試程式碼生成方案,這個方案唯一的缺點就是你需要不少的時間去做程式碼生成工具和相關自動化工具的編寫,開發成本頗高,可能要一直根據需求長期維護,屬於把難題留給了自己,把方便留給了別人,前人栽樹後人乘涼,做不好涼涼,做好了後人會:"聽我說謝謝你,因為有你,溫暖了四季..."。
或者你是個技術大佬,喜歡研究技術,這點東西就是個小菜兒,以下是程式碼生成方案簡述:

程式碼生成的方案:
優點:效能最高,通用性好(IL2CPP下也可使用)
缺點:實現複雜,費時間。

通過Unity強大的Editor 自己寫一套程式碼生成工具,把相關需要呼叫欄位(屬性)的功能都用程式碼生成。

比如我現在正在寫一個二進位制的資料格式,要實現物件序列化和反序列化的功能,在轉換物件的過程中需要呼叫物件的欄位(屬性),假設當前操作的物件型別為:
【UserInfo】

public class UserInfo {

    public int id;
    public string name;

}

程式碼生成的序列化類則為:
【UserInfoSerializer】

public static class UserInfoSerializer {

    /// <summary> 序列化 </summary>
    public static byte[] Serialize(UserInfo obj) {
        if (obj == null) return new byte[0];
        BinaryWriter writer = new BinaryWriter();
        writer.WriteInt32(obj.id);
        writer.WriteUnicode(obj.name);
        return writer.ToBytes();
    }

    /// <summary> 反序列化 </summary>
    public static UserInfo Deserialize(byte[] bytes) {
        if (bytes == null) return null;
        UserInfo obj = new UserInfo();
        if (bytes.Length == 0) return obj;
        BinaryReader reader = new BinaryReader(bytes);
        obj.id = reader.ReadInt32();
        obj.name = reader.ReadUnicode();
        return obj;
    }

}

測試程式碼:

private void SerializeTest() {
    // 初始化物件
    UserInfo user = new UserInfo();
    user.id = 1001;
    user.name = "A";

    // 序列化
    byte[] bytes = UserInfoSerializer.Serialize(user);

    // 反序列化
    UserInfo u = UserInfoSerializer.Deserialize(bytes);

    // 輸出資料
    print(u.id);   // 1001
    print(u.name); // "A"
}

測試程式碼通過,這個程式碼生成方案是行得通的,由於是具體型別直接呼叫,沒有任何反射、字典查詢、型別轉換和裝箱拆箱的消耗,效能極好(因為這就是直接呼叫),接下來就是要做易用性和自動化處理。

我們在Unity Editor中需要實現的功能大致如下:

在Unity中,選中一個我們需要序列化的類,右鍵選單 > 生成序列化幫助類,以便呼叫。

這種生成最好別對原來的程式碼進行任何修改,每次生成只參考目標類,生成序列化類,不對目標類進行改動,這樣重新生成的時候只要覆蓋生成類就好。

總結:

有時候看似最笨,最麻煩的方案,反而是最好的,也許這就是返璞歸真,重劍無鋒,大巧不工吧。

想要最佳的效能,直接呼叫才是最好的方案,如何解決直接呼叫的問題,用程式碼生成,Unity有著強大的Editor和各種介面,如果有時間和信心,可以嘗試一下程式碼生成方案,但因為可能消耗很多時間,這裡就不推薦了,各位根據自己的喜好選擇就好。總之先做完,再完善。

相關文章