![a7c9063c5679885b6bb06b71988c674f.png](https://i-blog.csdnimg.cn/blog_migrate/480b190259e64418b2f5c263c0dab97a.jpeg)
反射允许我们在程序运行时做一些非常动态的操作,如通过以字符串的形式调用函数。因此,当程序需要非常高的灵活性时,这是一个非常强大的工具。但是,它真的非常非常的慢。今天我们将测试反射与常规代码的性能差异到底有多大。你还可以了解到如何使用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
得到的测试结果如下:
Test | Reflection Time | Direct Time |
Get Type | 9 | 0 |
Get Filed | 8605 | 0 |
Get Property | 11994 | 0 |
Get Method | 11560 | 0 |
Read Field | 7460 | 7 |
Read Property | 13300 | 7 |
Write Field | 10840 | 6 |
Write Property | 15211 | 5 |
Call Method | 11750 | 4 |
通过测试得知,反射版本的所有函数都真的非常慢。在直接调用的版本中,前四个调用(GetType,GetField,GetProperty和GetMethod)几乎是没有性能消耗的。在反射版本中,GetType相当快,但是其他函数调用就非常慢了。
在剩余的测试中(读、写、调用),反射版本与直接调用版本相比仍然有巨大差异。从上面的结果可以看出,反射操作的速度大约比直接调用的速度慢10000倍。
简单的说,在使用反射时要三思而后行,权衡后再决定。每帧中调用几次没有什么大问题,但是如果是成千上万次的话,就需要认真思考一下了。如果必须使用反射,可以考虑缓冲GetMethod之类的结果。因为这类函数的调用非常消耗性能。并且它们的调用结果是不会变化的。