空值校验
我们来看下面的两段代码:
首先 dungeon_map 是 继承 MonoBehaviour 的一个类
if(dungeon_map is null)
{
Debug.LogError("[Hero3D] DungeonMap is null!");
return;
}
if(dungeon_map == null)
{
Debug.LogError("[Hero3D] DungeonMap is null!");
return;
}
这是我们比较常见的2种空值比较,但是两者的性能却相差非常多,下面我们对比 IL 代码看看发生了什么
is null
这里是 is null 中关键的 IL 代码
IL_0000: nop // 什么都不做
IL_0001: ldarg.1 // 将dungeon_map 推入评价堆栈
IL_0002: ldnull // 将空值推入评价堆栈
IL_0003: ceq // 比较两个值,并把结果存入评价堆栈
IL_0005: stloc.0 // 从评价堆栈取出一个值,并设置到序号为0的本地变量
IL_0006: ldloc.0 // 读取序号为0的本地变量,并把值存入评价堆栈
IL_0007: brfalse.s IL_0017 // 如果为 false 跳转到 IL_0017
IL_0009: nop
IL_000a: ldstr "[Hero3D] DungeonMap is null!"
IL_000f: call void [UnityEngine.CoreModule]UnityEngine.Debug::LogError(object)
IL_0014: nop
IL_0015: br.s IL_003c
== null
这里是 == null 中关键的 IL 代码
IL_0000: nop // 什么都不做
IL_0001: ldarg.1 // 将dungeon_map 推入评价堆栈
IL_0002: ldnull // 将空值推入评价堆栈
// 这里就不一样了,这里的比较交由Unity 的 Equality 进行判断中间还有装箱和拆箱
IL_0003: call bool [UnityEngine.CoreModule]UnityEngine.Object::op_Equality(class [UnityEngine.CoreModule]UnityEngine.Object, class [UnityEngine.CoreModule]UnityEngine.Object)
IL_0008: stloc.0
IL_0009: ldloc.0
IL_000a: brfalse.s IL_001a
IL_000c: nop
IL_000d: ldstr "[Hero3D] DungeonMap is null!"
IL_0012: call void [UnityEngine.CoreModule]UnityEngine.Debug::LogError(object)
IL_0017: nop
IL_0018: br.s IL_003f
结论
在 Unity 项目中, 凡是继承了 UnityEngine.Object 在使用 等于、等等、不等于 等,这些操作符时,都会被 UnityEngine.Object 中 Equals 函数接管,中间会发生装箱和拆箱,所以尽量使用 is null 进行空值判断
在项目中对
UnityEngine.Object
增加正确的空值校验扩展
/// <summary>
/// 针对 <see cref="Object"/> 为空的高效判断
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static bool IsNull(this Object obj)
{
return obj is null;
}
/// <summary>
/// 针对 <see cref="Object"/> 不为空的高效判断
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static bool IsNotNull(this Object obj)
{
return!(obj is null);
}