首页 > Unity3D频道 > 【Unity3D研究院之游戏开发】 > Unity3D研究院之UnityEngine.Object和System.Object(一百一十五)
2020
04-17

Unity3D研究院之UnityEngine.Object和System.Object(一百一十五)

最近在使用C#7的语法时遇到一个挺有意思的问题,深究其原理后发现值得写一篇文章。在C#中任何一个class类即使不写class XXX :System.Object 也会被默认继承System.Object,在C#中还有一个object关键字它和System.Object是一回事。所以UnityEngine.Object其实就是继承了System.Object的一个普通类对象,现在我们写一个普通的脚本

在写一个脚本,我们把它挂在一个空的游戏对象上,保证游戏对象提前没有被绑定过Camera组件和CustomSript组件,神奇的一目出现了。

为什么(GetComponent<Camera>() == null)结果是true但是((object)GetComponent<Camera>() == null);结果就成了false了呢?而自己写的脚本CustomSript两次输出的结果都是一致的,显然系统自带的组件和自己写的脚本是有区别的,但是它们有什么区别呢?

通过打断点你可以发现即使游戏对象没有绑定Camera组件,其实 var a = GetComponent<Camera>() 也是不为空的,原因就出在GetComponent这里,它返回系统自带组件永远都不为空,而返回自定义脚本则根据情况返回空。其实Unity就是用了重载 == 、!=、bool运算符让你得到了看似正确的结果。接着我们也来写个类模拟一下它的原理类。

这里我们定义了一个class A类,并且重载了==和!=运算符,即使A对象不等于null也要同时满足isAlive等于true的情况下才视为A对象不为Null。

来看代码吧,虽然a对象已经被new了,但是 (a==null)还是返回了true,只到我们设置了a.isAlive=true后,在判断(a==null)才返回false.

接着在A类中继续重写bool运算符

就可以if(a)来判断了,此时你会发现”A1″不会被打印出来而”A2″才会被打印出来。

所以这就解释为什么??符号在处理自己写的脚本不报错,而处理系统脚本就会报错。原因是GetComponent<系统自带脚本>永远都不会返回null,所以还是下面的例子,如果挂在空对象上就会发现Camera组件不会被正常添加,而CustomScript自己写的脚本会被正常添加。

还有在处理夺命连环null的情况下

Unity3D研究院之UnityEngine.Object和System.Object(一百一十五) - 雨松MOMO程序研究院 - 1

会提示 MissingComponentException: There is no ‘Camera’ attached to the “XXXXXXXX” game object, but a script is trying to access it.错误。请注这并不是报错,而是被C++底层抛出一个叫MissingComponentException的异常,因为GetComponent<Camera>()并不为空,所以它可以试图去访问gameObject对象。

所以在使用 ?? 和 ?. 语法时,只要记住别使用在Unity系统自带的组件上就行,其他地方自己的脚本或者自己写的类都完全没问题。

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

Unity3D研究院之UnityEngine.Object和System.Object(一百一十五)》有 3 条评论

  1. SunHowe 说:

    补充一点 当尝试访问一个被销毁的组件时,在Editor上会抛出MissingComponentException,但在真机(Android)上运行时,触发的Exception是NullReferenceException。注意这里组件的引用本身不为null, 只是被销毁了。

  2. pankycat 说:

    这篇文章有一定误导性,之前我已经在微博上给博主提了,但是博主貌似没有完全理解问题的关键。
    问题不在于内置组件GetComponent返回是否是空。而是在于==null这个写法的语义。
    Unity官方重写了所有UnityEngine.Object的==nul和!=null。==null 不仅会判断C#对象是否是null,也会判断底层的C++对象是否Destroy。
    就是说如果你写了if(go != null)(go可以是gameObject,也可以是任何MonoBehaviour),那么只要go destroy了,C#这边的引用是否置空就无关紧要了,下面的代码都不会执行。
    而现在?.和??这类新特性,只会判断C#的引用是否置空,不会考虑C++底层的对象是否已经Destroy。你也别想override这几个操作符,C#不允许你override他们。
    如果在项目中批量的将大量==null替换为.?写法,会造成原来不会走到的分支被走到、并且触发异常。
    做这种修改前请一定小心仔细并仔细测试。个人建议是不要在GameObject和MonoBehaviour上使用这几个操作符,除非你非常确定自己置空了所有引用。
    Unity的这个特性今天看来有局限性,当使用泛型方法的时候还有更大的坑。官方对此也非常纠结。但是为了兼容老的代码短期内应该不会修改。
    如果你使用Rider作为IDE,这种写法会直接报警,仔细查看说明确保你知道自己在做什么。
    参考链接:
    https://issuetracker.unity3d.com/issues/c-number-6-null-conditional-access-operator-dot-throws-missingcomponentexception-instead-of-recognizing-as-null
    https://forum.unity.com/threads/unity-2017-and-null-conditional-operators.489176/
    https://github.com/JetBrains/resharper-unity/wiki/Possible-unintended-bypass-of-lifetime-check-of-underlying-Unity-engine-object

留下一个回复

你的email不会被公开。