8

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

 3 years ago
source link: https://www.xuanyusong.com/archives/4713
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
专注移动互联网,专注Unity3D游戏开发
首页 > Unity3D频道 > 【Unity3D研究院之游戏开发】 > Unity3D研究院之UnityEngine.Object和System.Object(一百一十五)

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

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

public class CustomScript : MonoBehaviour{}

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

public class Test : MonoBehaviour
    void Start()
        bool flag = GetComponent<Camera>() == null;
        bool flag1 = GetComponent<CustomScript>() == null;
        Debug.Log(flag + " " + flag1); // log:  true , true
        flag = ((object)GetComponent<Camera>() == null);
        flag1 = ((object)GetComponent<CustomScript>() == null);
        Debug.Log(flag + " " + flag1); // log:  false , true

为什么(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。

    class A
        public bool isAlive=false;
        private static bool CompareBaseObjects(A lhs, A rhs)
            bool flag = (object)lhs == null;
            bool flag2 = (object)rhs == null;
            if (flag2 && flag)
                return true;
            if (flag2)
                return !lhs.isAlive;
            if (flag)
                return !rhs.isAlive;
            return false ;
        public static bool operator ==(A x, A y)
            return CompareBaseObjects(x, y);
        public static bool operator !=(A x, A y)
            return !CompareBaseObjects(x, y);

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

        A a = new A();
        bool flag = (a == null);
        bool flag1 = ((object)a == null);
        Debug.Log(flag + " " + flag1); // log:  true , false
        a.isAlive = true;
        flag = (a == null);
        flag1 = ((object)a == null);
        Debug.Log(flag + " " + flag1); // log:  false , false

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

        public static implicit operator bool(A exists)
            return !CompareBaseObjects(exists, null);

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

        A a = new A();
        if (a)
            Debug.Log("A1");
        a.isAlive = true;
        if (a)
            Debug.Log("A2");
        a.isAlive = true;

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

       var camera =  GetComponent<Camera>() ?? gameObject.AddComponent<Camera>();//Camera组件不会被添加
       var custom =  GetComponent<CustomScript>() ?? gameObject.AddComponent<CustomScript>(); //CustomScript组件正常被添加

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

Unity3D研究院之UnityEngine.Object和System.Object(一百一十五) - 雨松MOMO程序研究院 - 1
        GetComponent<Camera>()?.gameObject.SetActive(true);//报错
        GetComponent<CustomScript>()?.gameObject.SetActive(true);//不报错

会提示 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. af6a7dc1c459d88d797fd14b7a3a2741?s=80SunHowe 说:

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

  2. 4ccf2b4a1a536e692925dce81ce9bb65?s=80pankycat 说:

    这篇文章有一定误导性,之前我已经在微博上给博主提了,但是博主貌似没有完全理解问题的关键。
    问题不在于内置组件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不会被公开。

评论

姓名 *

电子邮件 *

站点

返回顶部 网站地图   ©2012-2020 XUANYUSONG.COM 雨松MOMO | Theme frontopen2

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK