C#反射时调用GetValue/SetValue方法时的拆箱和装箱操作

什么是拆箱和装箱操作

.NET的类型分为两种,一种是值类型,另一种是引用类型。这两个类型的本质区别,值类型数据是分配在中,而引用类型数据分配在上。

  • 装箱操作: 值类型转化成引用类型;
  • 拆箱操作: 引用类型转化为值类型。

装箱和拆箱过程需要进行大量的计算。 对值类型进行装箱时,必须创建一个全新的对象。这可能比简单的引用赋值用时最多长 20 倍。 拆箱的过程所需时间可达赋值操作的4倍。[来源于微软官方文档]

对值类型装箱会在中分配一个对象实例,并将该值复制到新的对象中。在堆中的对象需要gc回收,另外就是栈性能比堆高, 对引用类型的拆箱, 需要进行一个类型检查, 其次需要将该值从实例复制到值类型变量中。

int i = 123;      // 值类型
object o = i;     // 装箱, 注意: 这里可以是隐式转换, 因为任何类型都派生自Object
int j = (int)o;   // 拆箱, 注意: 这里必须显示转换, 这里可能出现异常(类型检查时)

GetValue/SetValue哪一步会发生装箱或拆箱

我们经常用到反射, 在反射时, 赋值/读取值时, 如果是值类型的数据, 通常会发生装箱和拆箱操作, 从而降低性能, 那么其在哪些写法中会发生装拆箱操作呢?那种写法性能更高?有什么优化方法?

// 如果传入的value是值类型, 其内部定义的字段也是值类型, 那么会先装箱,后拆箱 
public void SetValue(object? obj, object? value)

// 这里有两个调用写法, 当获取类实例的某个字段且字段是值类型, 因为 field.GetValue(obj) 返回的是一个object类型
// int aaa = (int)field.GetValue(obj)时, 会发生一次装箱和拆箱操作
// object aaa = field.GetValue(obj)时, 会发生一次装箱操作, 但是aaa在具体使用是会发生一次拆箱操作
public abstract object? GetValue(object? obj);

常规反射

点击这里查看C#反射

高性能反射

大量反射时建议

  • 缓存反射结果(MethodInfo/FieldInfo/PropertyInfo等):如果你多次执行相同的反射操作,可以考虑将结果缓存起来,以避免重复执行相同的反射调用。这可以显著提高性能。一般使用Dictionary<Type, xxx>方式将其缓存起来。
  • 避免在循环中使用:反射反射操作应该尽可能少地执行。如果你在循环中使用反射,每次迭代都会执行反射操作,这会导致性能显著下降。
  • 使用泛型委托:可以通过泛型委托将反射调用的开销转移到应用启动时或第一次调用时,然后重复使用该委托执行相同的反射调用。

Delegate.CreateDelegate

Delegate.CreateDelegate 是 C# 中的一个方法,能够在运行时动态创建委托。

通过使用Delegate.CreateDelegate代替传统反射(Type.GetMethodMethodInfo.Invoke),可以获得更好的性能。在调用方法时,委托比反射更有效,因为它们提供了对方法的直接调用,而没有反射的开销。

用Emit生成IL代码: System.Reflection.Emit

(见下面测试代码)
如果需要学习Emit库和IL相关知识,最好找个时间整体学习一下。

表达式树:Expression Tree

(见下面测试代码)

扩展知识

众所周知, 值类型不受gc管理,一般存储在栈中(对象的值类型的属性就存储在堆中, 局部变量值类型才分配在栈中), 其性能相对于引用类型高, 但是不是所有的值类型的性能都高, 因为值类型在方法调用时是复制其值的副本给方法调用的, 所以当一个值类型比较大时, 其方法调用会降低性能。

如何避免大的值类型的复制呢? 可以将大的值类型做为一个类的属性, 然后方法调用时将类的实例传给方法, 这样就可以使用类实例.实例的属性.值类型属性 = 111方式调用, 从而减少大的值类型复制。同样使用ref MyStruct aaa的方式将大结构的应用传递给方法也可以提高效率。

测试反射各种方法的性能

using System;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using BenchmarkDotNet;
using BenchmarkDotNet.Attributes;

namespace test
{

    [AttributeUsage(AttributeTargets.Field)]
    public class BinaryOffset : Attribute
    {
        public int Offset { get; }

        public BinaryOffset(int offset)
        {
            Offset = offset;
        }
    }

    [AttributeUsage(AttributeTargets.Class)]
    public class BinaryClass : Attribute
    {
        public int Offset { get; }

        public BinaryClass(int offset)
        {
            Offset = offset;
        }
    }

    [BinaryClass(10)]
    public class MyClass
    {
        [BinaryOffset(0)]
        public int field1 = 10;

        [BinaryOffset(4)]
        public bool field2 = false;
        public uint field3 = 10;
        public byte field4 = 2;
        public string field5 = "test";

        public void testMethod()
        {
            Console.WriteLine("test...");
        }
    }

    [MemoryDiagnoser]
    public class ReflectionTest
    {
        readonly MyClass myObject;
        readonly Type type;
        readonly Action<MyClass, int> setFieldValueDelegate;
        readonly Func<MyClass, int> getFieldValueDelegate;
        readonly FieldInfo fieldInfo;
        readonly FrozenDictionary<int, int> frozenDictionary;

        public ReflectionTest()
        {
            myObject = new MyClass();
            type = typeof(MyClass);
            fieldInfo = type.GetField("field1");

            setFieldValueDelegate = CreateSetFieldDelegate<MyClass, int>(fieldInfo);
            getFieldValueDelegate = CreateGetFieldDelegate<MyClass, int>(fieldInfo);

            Dictionary<int, int> keyValuePairs = new Dictionary<int, int>();
            for (int i = 0; i < 2000; i++) {
                keyValuePairs[i] = i;
            }
            frozenDictionary = keyValuePairs.ToFrozenDictionary();
        }

        readonly int length = 10000;

        // 测试使用:Emit生成IL的方式获取字段值
        [Benchmark(Baseline = true)]
        public void getValueEmit()
        {
            for (int i = 0; i < length; i++)
            {
                getFieldValueDelegate(myObject);
            }
        }

        // 测试使用:Emit生成IL的方式修改字段值
        [Benchmark]
        public void setValueEmit()
        {
            for (int i = 0; i < length; i++)
            {
                setFieldValueDelegate(myObject, 20);
            }
        }

        // 测试使用:反射获取字段信息
        [Benchmark]
        public void getField()
        {
            for (int i = 0; i < length; i++)
            {
                type.GetField("field1");
            }
        }

        // 测试使用:反射获取方法信息
        [Benchmark]
        public void getMethod()
        {
            for (int i = 0; i < length; i++)
            {
                type.GetMethod("testMethod");
            }
        }

        // 测试使用:反射获取字段值
        [Benchmark]
        public void getValueRef()
        {
            for (int i = 0; i < length; i++)
            {
                fieldInfo.GetValue(myObject);
            }
        }

        // 测试使用:反射修改字段值
        [Benchmark]
        public void setValueRef()
        {
            for (int i = 0; i < length; i++)
            {
                fieldInfo.SetValue(myObject, 20);
            }
        }

        // 测试使用:反射获取字段特性
        [Benchmark]
        public void GetCustomAttributeField()
        {
            for (int i = 0; i < length; i++)
            {
                BinaryOffset customAttribute1 = fieldInfo.GetCustomAttribute<BinaryOffset>();
            }
        }

        // 测试使用:反射获取类特性
        [Benchmark]
        public void GetCustomAttributeClass()
        {
            for (int i = 0; i < length; i++)
            {
                BinaryClass customAttribute1 = type.GetCustomAttribute<BinaryClass>();
            }
        }

        // 测试使用:System.Activator类实例化
        [Benchmark]
        public void CreateInstanceActivator()
        {
            for (int i = 0; i < length; i++)
            {
                MyClass 当前封包 = (MyClass)Activator.CreateInstance(type);
            }
        }

        // 测试使用:表达式树实例化一个对象
        [Benchmark]
        public void CreateInstanceExpression1()
        {
            for (int i = 0; i < length; i++)
            {
                Func<MyClass> creator = InstanceCreatorCache.GetOrCreateInstanceCreator(type);
                MyClass 当前封包 = creator();
            }
        }

        // 测试使用:表达式树实例化一个对象
        [Benchmark]
        public void CreateInstanceExpression2()
        {
            for (int i = 0; i < length; i++)
            {
                var myInstance = InstanceFactory.CreateInstance<MyClass>();
            }
        }

        // 测试-冻结字典查询效率
        [Benchmark]
        public void FrozenDictionaryGet()
        {
            for (int i = 0; i < length; i++)
            {
                frozenDictionary.TryGetValue(1981, out int aaa);
            }
        }

        /// <summary>
        /// 设置字段的值, 委托方法
        /// </summary>
        /// <typeparam name="TObj"></typeparam>
        /// <typeparam name="TField"></typeparam>
        /// <param name="fieldinfo"></param>
        /// <returns></returns>
        private static Action<TObj, TValue> CreateSetFieldDelegate<TObj, TValue>(FieldInfo fieldinfo)
        {
            var method = new DynamicMethod("SetField", typeof(void), [typeof(TObj), typeof(TValue)], typeof(Program).Module);
            ILGenerator il = method.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Stfld, fieldinfo);
            il.Emit(OpCodes.Ret);

            return (Action<TObj, TValue>)method.CreateDelegate(typeof(Action<TObj, TValue>));
        }

        /// <summary>
        /// 获取字段的值, 委托方法
        /// </summary>
        /// <typeparam name="TObj"></typeparam>
        /// <typeparam name="TField"></typeparam>
        /// <param name="fieldinfo"></param>
        /// <returns></returns>
        private static Func<TObj, TField> CreateGetFieldDelegate<TObj, TField>(FieldInfo fieldinfo)
        {
            var method = new DynamicMethod("GetField", typeof(TField), [typeof(TObj)], typeof(Program).Module);
            ILGenerator il = method.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldfld, fieldinfo);
            il.Emit(OpCodes.Ret);

            return (Func<TObj, TField>)method.CreateDelegate(typeof(Func<TObj, TField>));
        }

        /// <summary>
        /// 表达式树,实例化对象, 委托, 并缓存
        /// </summary>
        public static class InstanceCreatorCache
        {
            private static readonly ConcurrentDictionary<Type, Func<MyClass>> Cache = new ConcurrentDictionary<Type, Func<MyClass>>();

            public static Func<MyClass> GetOrCreateInstanceCreator(Type type)
            {
                return Cache.GetOrAdd(type, t =>
                {
                    var constructor = t.GetConstructor(Type.EmptyTypes) ?? throw new InvalidOperationException($"没有无参数构造函数: {t.FullName}");
                    var newExpression = Expression.New(constructor);
                    var lambda = Expression.Lambda<Func<MyClass>>(newExpression);
                    return lambda.Compile();
                });
            }
        }

        /// <summary>
        /// 上一个的泛型写法
        /// </summary>
        public static class InstanceFactory
        {
            private static readonly ConcurrentDictionary<Type, Func<object>> _instanceCache = new ConcurrentDictionary<Type, Func<object>>();

            public static T CreateInstance<T>()
            {
                return (T)CreateInstance(typeof(T));
            }

            public static object CreateInstance(Type type)
            {
                if (_instanceCache.TryGetValue(type, out var factory))
                {
                    return factory();
                }

                var newExpression = Expression.New(type);
                var lambdaExpression = Expression.Lambda<Func<object>>(newExpression);
                factory = lambdaExpression.Compile();

                _instanceCache.TryAdd(type, factory);
                return factory();
            }
        }

    }

}

Method Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio
getValueEmit 21.63 us 0.404 us 0.539 us 21.46 us 1.00 0.00 - - NA
setValueEmit 21.81 us 0.275 us 0.230 us 21.75 us 1.01 0.03 - - NA
getField 116.79 us 2.226 us 2.734 us 116.53 us 5.40 0.18 - - NA
getMethod 216.06 us 4.221 us 6.317 us 215.39 us 10.01 0.44 - 2 B NA
getValueRef 465.69 us 9.281 us 15.507 us 459.33 us 21.52 1.04 28.3203 240003 B NA
setValueRef 522.79 us 10.164 us 9.982 us 517.75 us 24.25 0.65 28.3203 240006 B NA
GetCustomAttributeField 7,912.32 us 156.534 us 286.231 us 7,774.19 us 368.95 14.79 234.3750 2000012 B NA
GetCustomAttributeClass 7,739.98 us 129.195 us 201.142 us 7,760.01 us 357.51 13.47 234.3750 2000012 B NA
CreateInstanceActivator 85.55 us 1.755 us 4.863 us 84.42 us 4.16 0.22 47.7295 400001 B NA
CreateInstanceExpression1 130.45 us 2.448 us 4.598 us 129.47 us 6.11 0.26 47.6074 400000 B NA
CreateInstanceExpression2 140.51 us 2.788 us 4.173 us 139.96 us 6.50 0.26 47.6074 400000 B NA
FrozenDictionaryGet 22.44 us 0.239 us 0.186 us 22.52 us 1.04 0.03 - - NA

备注:使用的是.NET8.0, 从测试数据中有几个特别要注意的地方:

  • 使用反射获取Attribute非常耗时
  • 使用Emit生成IL的方式动态修改属性值并委托后,比使用反射修改/获取效率高很多,且没有装箱和拆箱问题,但是如果需要反射的字段太多,委托缓存会大量占用内存,如果不缓存又会降低效率。

扩展阅读

装箱和取消装箱(C# 编程指南)
晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo
三种属性操作性能比较:PropertyInfo + Expression Tree + Delegate.CreateDelegate
关于Expression Tree和IL Emit的所谓的"性能差别"
C# 表达式树 Expression Trees知识总结
.NET高级特性-Emit
C#(含Unity)unsafe指针快速反射第一篇(字段篇 )

此处评论已关闭