3

Original address: https://segmentfault.com/a/1190000041978404

This article mainly talks about how to optimize the reflection performance of getting (setting) object fields (attributes).

Conclusion first:

1. Reflection performance is poor, hundreds or thousands of times slower than direct calls.
2. The idea of reflection performance optimization is to bypass reflection.
3. Reflection performance optimization methods include: Dictionary Cache, Delegate (Delegate.CreateDelegate), Expression.Lamda, IL.Emit, Pointer (IntPtr) and other methods.
4. Performance ranking: direct call > (IL.Emit == Expression) > delegate > pointer > reflection
5. Versatility ranking: Reflection > (Delegate == Pointer) > (IL.Emit == Expression) > Direct call
6. Comprehensive ranking: (Delegate == Pointer) > (IL.Emit == Expression) > Reflection > Direct call

IL.Emit is the best performance solution, but unfortunately IL2CPP mode does not support JIT. All the functions of dynamically generating code are not easy to use, so IL.Emit cannot be used, and Expression.Lamda is also unavailable for the same reason.
The next best thing is to choose a delegate with good performance and versatility. Since the delegate can only optimize the method and cannot be used for the field, a pointer is added to operate the field.


Performance Testing:

The test condition is to set the property Name of the object (obj.Name = "A")
The SetValue methods are all adjusted to the general SetValue(object target, object value) state.
Except for reflection, all calling methods are called after creating a delegate, omitting the step of finding the delegate cache.
In Release mode, loop multiple calls to check the time-consuming.
If the direct call takes about 1ms, the other methods take about:

call directly IL.Emit Expression Delegate reflection (MethodInfo)
1ms 1.3ms 1.3ms 2.1ms 91.7ms

The pointer only operates on the field and does not participate in this test

If the test condition is changed to call the delegate method corresponding to the field from the cache through Dictionary every time you get or set a value, then the time will be greatly increased, and most of the time will be wasted on dictionary lookup, but this is also closer Our normal use case.

call directly = IL.Emit Expression Delegate reflection (MethodInfo)
1ms 8.8ms 8.8ms 9.7ms 192.5ms

Set up the string field test:

call directly = IL.Emit Expression pointer reflection (MethodInfo)
1ms 8ms 8ms 9ms 122ms

Delegate only operates properties and does not participate in this test

From the test results, it can be seen that direct call > IL.Emit (Expression is consistent with IL) > delegate (pointer and delegate are complementary) > reflection

Basically, as long as the reflection can be bypassed, the performance will be significantly improved.


IL.Emit

Advantages: very high performance.
Cons: IL2CPP is not supported.

IL.Emit general tool class:

usage:

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

Code:

 #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

Advantages: very high performance.
Cons: IL2CPP is not supported.
Same as IL.Emit.

Expression generic tool class:

usage:

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

Code:

 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

Advantages: high performance, strong versatility (can also be used under IL2CPP).
Disadvantages: Only methods can be manipulated, fields cannot be manipulated directly

Delegate generic utility class:
usage:

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

Code:

 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();
    }

}

pointer

Advantages: high performance, good versatility (can also be used under IL2CPP).
Disadvantage: I don't know how to manipulate properties (methods) with pointers for the time being.

Pointer general utility class:

 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();
    }

}

Optimal solution selection:

Which of the many optimization schemes to use? Is there the best one? (You can refer to the following suggestions)

1. The IL.Emit scheme is the first choice, the performance is very good, the only disadvantage is that it is not compatible with the IL2CPP mode.

2. The second-choice delegate (Delegate.CreateDelegate) scheme is used to replace IL.Emit in IL2CPP mode. Since delegates can only operate methods and cannot operate fields, try to write them in the form of {get; set;}, if there are fields required operation, then add the pointer scheme (IntPtr).

The ultimate optimization method:

If you have tried all the above methods and you are still not satisfied, as an adult, you want to have both fish and bear's paw, the highest performance, the best versatility, and the convenience and ease of use, you can try Code generation solution, the only disadvantage of this solution is that you need a lot of time to write code generation tools and related automation tools. The development cost is quite high, and you may have to maintain it for a long time according to your needs. The convenience is left to others. The predecessors planted the trees and the later generations enjoyed the shade. If you can't do it well, the future generations will say: "Listen to me, thank you, because of you, the four seasons have been warmed...".
Or you are a big tech guy who likes to study technology. This is a small thing. The following is a brief description of the code generation scheme:

Code generation scheme:
Advantages: Highest performance, good versatility (also available under IL2CPP)
Disadvantages: complex and time-consuming to implement.

Write a set of code generation tools through Unity's powerful Editor, and use code to generate the functions related to the fields (attributes) that need to be called.

For example, I am currently writing a binary data format. To realize the function of object serialization and deserialization, I need to call the fields (attributes) of the object in the process of converting the object. Suppose the object type of the current operation is:
【UserInfo】

 public class UserInfo {

    public int id;
    public string name;

}

The serialization class generated by the code is:
【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;
    }

}

Test code:

 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"
}

The test code is passed, and this code generation scheme is feasible. Since the specific type is called directly, there is no consumption of reflection, dictionary lookup, type conversion and boxing and unboxing, and the performance is excellent (because this is the direct call). The next thing to do is ease of use and automation.

The functions we need to implement in the Unity Editor are roughly as follows:

In Unity, select a class that we need to serialize, right-click menu > Generate Serialization Helper Class for calling.

It is best not to make any modifications to the original code for this kind of generation. Each generation only refers to the target class, generates the serialized class, and does not make changes to the target class. In this way, only the generated class needs to be overwritten when regenerating.

Summarize:

Sometimes it seems that the most stupid and troublesome plan is the best. Maybe this is the return to the basics.

If you want the best performance, direct invocation is the best solution. How to solve the problem of direct invocation? Using code generation, Unity has a powerful Editor and various interfaces. If you have time and confidence, you can try the code generation solution. But because it may consume a lot of time, it is not recommended here. You can choose according to your own preferences. In short, finish first, then perfect.


冰封百度
233 声望43 粉丝

Unity游戏程序员一枚。生命不息,学习不止。