写入 反射_C#反射的性能究竟如何?

本文探讨了C#反射在程序运行时的动态操作,并通过测试揭示了反射与直接代码调用的性能差异。测试结果显示,反射操作速度通常比直接调用慢约10000倍,尤其是在频繁调用的情况下。建议在使用反射时谨慎考虑,如果必须使用,可以考虑缓存如GetMethod的结果以提高性能。
摘要由CSDN通过智能技术生成

a7c9063c5679885b6bb06b71988c674f.png

反射允许我们在程序运行时做一些非常动态的操作,如通过以字符串的形式调用函数。因此,当程序需要非常高的灵活性时,这是一个非常强大的工具。但是,它真的非常非常的慢。今天我们将测试反射与常规代码的性能差异到底有多大。你还可以了解到如何使用C#反射。

C#反射操作大多位于System.Reflection命名空间中,但反射操作是基于System.Type类的。每个变量在C#中都有一个Type字段,你可以通过以下两种方式获得:

// get the Type of class
var classType = typeof(TestClass);

// get the Type of variable
var classType = classInstance.GetType();

获取到Type之后就能获取它的字段,属性,方法等等。这些信息都被定义在System.Reflection命名空间中以”Info”结尾的类中,如:FieldInfo,PropertyInfo,MethodInfo等。下面是获取它们的方式:

// Get the field named "MyField"
var fieldInfo = classType.GetField("MyField");

// Get the property named "MyProperty";
var propertyInfo = classType.GetProperty("MyProperty");

// Get the method/function named "MyMethod";
var methodInfo = classType.GetMethod("MyMethod");

一旦获取到这些,就可以像直接编写代码一样调用Info中的方法来操作对象。可以读取和写入字段值,属性以及调用方法。下面是这些操作的使用方式:

// Read and or write a field from an instance of TestClass
fieldInfo.GetValue(classInstance);
fieldInfo.SetValue(classInstance, 123);

// Read and or write a property from an instance of TestClass
// The last parameter is null for non-indexed properties
propertyInfo.GetValue(classInstance, null);
propertyInfo.SetValue(classInstance, 123, null);

// Call a method on an instance of TestClass
var paramters = new object[] { 1, 2, 3 };
methodInfo.Invoke(classInstance, paramters);

接下来,我们来看一下将反射与直接调用作比较的简单测试。

using System;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using UnityEngine;

public static class StopwatchExtensions
{
    public delegate void TestFunction();

    public static long RunTest(this Stopwatch stopwatch, TestFunction testFunc)
    {
        stopwatch.Reset();
        stopwatch.Start();
        testFunc();

        return stopwatch.ElapsedMilliseconds;
    }
}

public class TestClass
{
    public int intField;

    public int intProperty { get; set; }

    public void VoidMethod() { }
}

public class Test : MonoBehaviour
{
    private const int _numIterations = 10000000;

    private StringBuilder _reportBuilder = new StringBuilder(200);
    private string _report = string.Empty;

    private Rect _drawRect = Rect.zero;

    private void Awake()
    {
        _drawRect = new Rect(0, 0, Screen.width, Screen.height);
    }

    private void Start()
    {

        var stopwatch = new Stopwatch();

        Type testClassType = null;
        FieldInfo fieldInfo = null;
        PropertyInfo propertyInfo = null;
        MethodInfo methodInfo = null;
        var intValue = 0;
        var testClassInstance = new TestClass();

        var getTypeTime = stopwatch.RunTest(() => {
            for (int index = 0; index < _numIterations; ++index)
            {
                testClassType = typeof(TestClass);
            }
        });

        var getFieldTime = stopwatch.RunTest(()=> {
            for (int index = 0; index < _numIterations; ++index)
            {
                fieldInfo = testClassType.GetField("intField");
            }
        });

        var getPropertyTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                propertyInfo = testClassType.GetProperty("intProperty");
            }
        });

        var getMethodTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                methodInfo = testClassType.GetMethod("VoidMethod");
            }
        });

        var readFieldReflectionTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                intValue = (int)fieldInfo.GetValue(testClassInstance);
            }
        });

        var readPropertyReflectionTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                intValue = (int)propertyInfo.GetValue(testClassInstance, null);
            }
        });

        var readFieldDirectTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                intValue = testClassInstance.intField;
            }
        });

        var readPropertyDirectTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                intValue = testClassInstance.intProperty;
            }
        });

        var writeFieldReflectionTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                fieldInfo.SetValue(testClassInstance, 5);
            }
        });

        var writePropertyReflectionTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                propertyInfo.SetValue(testClassInstance, 5, null);
            }
        });

        var writeFieldDirectTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                testClassInstance.intField = 5;
            }
        });

        var writePropertyDirectTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                testClassInstance.intProperty = 5;
            }
        });

        var callMethodReflectTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                methodInfo.Invoke(testClassInstance, null);
            }
        });

        var callMethodDirectTime = stopwatch.RunTest(() =>
        {
            for (int index = 0; index < _numIterations; ++index)
            {
                testClassInstance.VoidMethod();
            }
        });

        _reportBuilder.AppendLine("Test, Reflection Time, Direct Time");
        _reportBuilder.AppendFormat("Get Type, {0} ,0n", getTypeTime);
        _reportBuilder.AppendFormat("Get Field, {0} ,0n", getFieldTime);
        _reportBuilder.AppendFormat("Get Property, {0} ,0n", getPropertyTime);
        _reportBuilder.AppendFormat("Get Method, {0} ,0n", getMethodTime);
        _reportBuilder.AppendFormat("Read Field, {0}, {1}n", readFieldReflectionTime, readFieldDirectTime);
        _reportBuilder.AppendFormat("Read Property, {0}, {1}n", readPropertyReflectionTime, readPropertyDirectTime);
        _reportBuilder.AppendFormat("Write Field, {0}, {1}n", writeFieldReflectionTime, writeFieldDirectTime);
        _reportBuilder.AppendFormat("Write Property, {0}, {1}n", writePropertyReflectionTime, writePropertyDirectTime);
        _reportBuilder.AppendFormat("Call Method, {0}, {1}n", callMethodReflectTime, callMethodDirectTime);

        _report = _reportBuilder.ToString();
    }

    private void OnGUI()
    {
        GUI.TextArea(_drawRect, _report);
    }
}

如果你想自己测试一下,将以上代码复制到Test.cs文件中,然后将其附加到场景中的任意一个GameObject上,然后编译打包就行。我的测试环境为:

1. 2.6 GHz Intel Core i7-6700HQ

2. Window 10

3. Unity 2019.2.13f1, PC,Mac & Linux Standalone, x86_64, non-development

得到的测试结果如下:

TestReflection TimeDirect Time
Get Type90
Get Filed86050
Get Property119940
Get Method115600
Read Field74607
Read Property133007
Write Field108406
Write Property152115
Call Method117504

通过测试得知,反射版本的所有函数都真的非常慢。在直接调用的版本中,前四个调用(GetType,GetField,GetProperty和GetMethod)几乎是没有性能消耗的。在反射版本中,GetType相当快,但是其他函数调用就非常慢了。

在剩余的测试中(读、写、调用),反射版本与直接调用版本相比仍然有巨大差异。从上面的结果可以看出,反射操作的速度大约比直接调用的速度慢10000倍。

简单的说,在使用反射时要三思而后行,权衡后再决定。每帧中调用几次没有什么大问题,但是如果是成千上万次的话,就需要认真思考一下了。如果必须使用反射,可以考虑缓冲GetMethod之类的结果。因为这类函数的调用非常消耗性能。并且它们的调用结果是不会变化的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值