空值校验

我们来看下面的两段代码:

首先 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.ObjectEquals 函数接管,中间会发生装箱和拆箱,所以尽量使用 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);  
}