NullReferenceException เป็นอันหนึ่งอันเดียวกัน


11

เนื่องจากผู้ใช้หลายคนกำลังเผชิญกับNullReferenceException: Object reference not set to an instance of an objectข้อผิดพลาดใน Unity ฉันคิดว่าการรวบรวมคำอธิบายและวิธีการแก้ไขข้อผิดพลาดนี้เป็นความคิดที่ดีที่จะรวบรวมจากหลายแหล่ง


อาการ

ฉันได้รับข้อผิดพลาดด้านล่างปรากฏในคอนโซลของฉันมันหมายถึงอะไรและฉันจะแก้ไขได้อย่างไร

NullReferenceException: การอ้างอิงวัตถุไม่ได้ตั้งค่าเป็นอินสแตนซ์ของวัตถุ


ดูเหมือนว่าคำถามทั่วไปเกี่ยวกับการเขียนโปรแกรมไม่ใช่เฉพาะเกม dev คำตอบสำหรับคำถามของตัวเอง OP มีลิงค์ไปยัง SO ซึ่งครอบคลุมหัวข้อนี้
Pikalek

3
ในขณะที่ "NullReferenceException" คือคำถามทั่วไปของการเขียนโปรแกรมที่นี่คำถามจะครอบคลุมถึงข้อยกเว้นเฉพาะใน Unityโดยสามารถพบได้ในการเขียนโปรแกรม Unityและวิธีแก้ไข (ดูตัวอย่างต่าง ๆ )
Hellium

@Pikalek เราได้ขยายขอบเขตของเราสำหรับสิ่งที่เราอนุญาตในแง่ของการเขียนโปรแกรมทั่วไป นี้ได้รับการชี้แจงเมื่อฉันถามเกี่ยวกับเรื่องนี้ในเมตา ตอนนี้ฉันรู้แล้วว่านี่อาจยังพอดีกับพารามิเตอร์ของ 'สามัญเกินไป' ตามคำตอบของ Josh
Gnemlock

คำตอบปัจจุบันยังทำให้ทราบไม่มีอะไรที่เฉพาะเจาะจงเพื่อความสามัคคี (นอกเหนือจากการใช้ประเภทเฉพาะความสามัคคีในตัวอย่าง) อันที่จริงแล้วมันคือการตอบสนองการเขียนโปรแกรมทั่วไป เราไม่ได้ใช้คำตอบในการโต้แย้งอย่างใกล้ชิด แต่เนื่องจากมันเป็นคำตอบด้วยตนเองมันจะไปสู่การสนับสนุนการโต้แย้งความตั้งใจ
Gnemlock

3
Unity มีวิธี / ลักษณะเฉพาะที่ไม่ซ้ำกันในการทริกเกอร์ข้อผิดพลาดเหล่านี้ผ่านช่องตรวจสอบที่ไม่ได้กำหนดมอบหมายล้มเหลว GetComponent หรือ Find พยายามหรือผ่านรสชาติที่แตกต่าง "MissingReferenceException" เมื่อคุณมีการอ้างอิงที่ถูกต้อง ดังนั้นฉันคิดว่าคำตอบสำหรับคำถามนี้ในบริบทของความสามัคคีมีศักยภาพที่ดีที่จะเป็นประโยชน์ต่อชุมชนแม้ว่าข้อยกเว้นนั้นจะกว้างมาก
DMGregory

คำตอบ:


14

ชนิดของค่าเทียบกับชนิดของการอ้างอิง

ในภาษาการเขียนโปรแกรมหลายตัวแปรตัวแปรมีสิ่งที่เรียกว่า "ชนิดข้อมูล" ชนิดข้อมูลหลักสองชนิดคือชนิดของค่า (int, float, bool, char, struct, ... ) และประเภทการอ้างอิง (อินสแตนซ์ของคลาส) ในขณะที่ชนิดของค่ามีค่าตัวเองการอ้างอิงประกอบด้วยที่อยู่หน่วยความจำที่ชี้ไปยังส่วนของหน่วยความจำที่จัดสรรให้มีชุดของค่า (คล้ายกับ C / C ++)

ตัวอย่างเช่นVector3เป็นประเภทค่า (โครงสร้างที่มีพิกัดและฟังก์ชั่นบางอย่าง) ในขณะที่ส่วนประกอบที่แนบมากับ GameObject ของคุณ (รวมถึงสคริปต์ที่กำหนดเองของคุณที่สืบทอดมาจากMonoBehaviour) เป็นประเภทอ้างอิง

เมื่อใดที่ฉันสามารถมี NullReferenceException

NullReferenceException จะถูกโยนทิ้งเมื่อคุณพยายามเข้าถึงตัวแปรอ้างอิงที่ไม่ได้อ้างอิงวัตถุใด ๆ ดังนั้นจึงเป็นโมฆะ (ที่อยู่หน่วยความจำชี้ไปที่ 0)

สถานที่ทั่วไปบางแห่งNullReferenceExceptionจะได้รับการยก:

การจัดการ GameObject / Component ที่ไม่ได้ระบุไว้ในสารวัตร

// t is a reference to a Transform.
public Transform t ;

private void Awake()
{
     // If you do not assign something to t
     // (either from the Inspector or using GetComponent), t is null!
     t.Translate();
}

การดึงส่วนประกอบที่ไม่ได้แนบมากับ GameObject แล้วพยายามจัดการมัน:

private void Awake ()
{
    // Here, you try to get the Collider component attached to your gameobject
    Collider collider = gameObject.GetComponent<Collider>();

    // But, if you haven't any collider attached to your gameobject,
    // GetComponent won't find it and will return null, and you will get the exception.
    collider.enabled = false ;
}

การเข้าถึง GameObject ที่ไม่มีอยู่:

private void Start()
{
    // Here, you try to get a gameobject in your scene
    GameObject myGameObject = GameObject.Find("AGameObjectThatDoesntExist");

    // If no object with the EXACT name "AGameObjectThatDoesntExist" exist in your scene,
    // GameObject.Find will return null, and you will get the exception.
    myGameObject.name = "NullReferenceException";
}

หมายเหตุ:ต้องระวัง, GameObject.Find, GameObject.FindWithTag, GameObject.FindObjectOfTypeเพียงกลับ gameObjects ที่มีการเปิดใช้งานในลำดับชั้นเมื่อฟังก์ชั่นที่เรียกว่า

กำลังพยายามใช้ผลลัพธ์ของผู้ทะเยอทะยานที่กลับมาnull:

var fov = Camera.main.fieldOfView;
// main is null if no enabled cameras in the scene have the "MainCamera" tag.

var selection = EventSystem.current.firstSelectedGameObject;
// current is null if there's no active EventSystem in the scene.

var target = RenderTexture.active.width;
// active is null if the game is currently rendering straight to the window, not to a texture.

การเข้าถึงองค์ประกอบของอาร์เรย์ที่ไม่ได้กำหนดค่าเริ่มต้น

private GameObject[] myObjects ; // Uninitialized array

private void Start()
{
    for( int i = 0 ; i < myObjects.Length ; ++i )
        Debug.Log( myObjects[i].name ) ;
}

พบได้น้อย แต่น่ารำคาญถ้าคุณไม่รู้เกี่ยวกับผู้รับมอบสิทธิ์ C #:

delegate double MathAction(double num);

// Regular method that matches signature:
static double Double(double input)
{
    return input * 2;
}

private void Awake()
{
    MathAction ma ;

    // Because you haven't "assigned" any method to the delegate,
    // you will have a NullReferenceException
    ma(1) ;

    ma = Double ;

    // Here, the delegate "contains" the Double method and
    // won't throw an exception
    ma(1) ;
}

จะแก้ไขอย่างไร

หากคุณเข้าใจย่อหน้าก่อนหน้านี้คุณรู้วิธีแก้ไขข้อผิดพลาด: ตรวจสอบให้แน่ใจว่าตัวแปรของคุณอ้างอิง (ชี้ไปที่) ตัวอย่างของคลาส (หรือมีอย่างน้อยหนึ่งฟังก์ชั่นสำหรับผู้รับมอบสิทธิ์)

พูดง่ายกว่าทำ? ใช่แน่นอน. นี่คือเคล็ดลับที่จะหลีกเลี่ยงและระบุปัญหา

วิธี "สกปรก": วิธีลองและจับ:

Collider collider = gameObject.GetComponent<Collider>();

try
{
    collider.enabled = false ;
}       
catch (System.NullReferenceException exception) {
    Debug.LogError("Oops, there is no collider attached", this) ;
}

วิธี "สะอาด" (IMHO): การตรวจสอบ

Collider collider = gameObject.GetComponent<Collider>();

if(collider != null)
{
    // You can safely manipulate the collider here
    collider.enabled = false;
}    
else
{
    Debug.LogError("Oops, there is no collider attached", this) ;
}

เมื่อหันหน้าไปทางข้อผิดพลาดที่คุณไม่สามารถแก้ปัญหาก็มักจะเป็นความคิดที่ดีที่จะหาสาเหตุของปัญหา หากคุณเป็น "ขี้เกียจ" (หรือหากปัญหาสามารถแก้ไขได้ง่าย) ให้ใช้Debug.Logเพื่อแสดงข้อมูลคอนโซลซึ่งจะช่วยให้คุณระบุสิ่งที่อาจทำให้เกิดปัญหา วิธีที่ซับซ้อนมากขึ้นคือการใช้เบรกพอยต์และดีบักเกอร์ของ IDE ของคุณ

การใช้Debug.Logค่อนข้างมีประโยชน์ในการกำหนดฟังก์ชั่นที่เรียกว่าเป็นตัวอย่างแรก โดยเฉพาะถ้าคุณมีหน้าที่รับผิดชอบในการเริ่มต้นเขตข้อมูล แต่อย่าลืมลบไฟล์เหล่านั้นออกDebug.Logเพื่อหลีกเลี่ยงความยุ่งเหยิงในคอนโซลของคุณ (และเพื่อเหตุผลด้านประสิทธิภาพ)

คำแนะนำอื่น ๆ อย่าลังเลที่จะ "ตัด" ฟังก์ชั่นการโทรของคุณและเพิ่มDebug.Logเพื่อตรวจสอบบางอย่าง

แทน :

 GameObject.Find("MyObject").GetComponent<MySuperComponent>().value = "foo" ;

ทำสิ่งนี้เพื่อตรวจสอบว่ามีการอ้างอิงทุกชุดหรือไม่:

GameObject myObject = GameObject.Find("MyObject") ;

Debug.Log( myObject ) ;

MySuperComponent superComponent = myObject.GetComponent<MySuperComponent>() ;

Debug.Log( superComponent ) ;

superComponent.value = "foo" ;

ดียิ่งขึ้น:

GameObject myObject = GameObject.Find("MyObject") ;

if( myObject != null )
{
   MySuperComponent superComponent = myObject.GetComponent<MySuperComponent>() ;
   if( superComponent != null )
   {
       superComponent.value = "foo" ;
   }
   else
   {
        Debug.Log("No SuperComponent found onMyObject!");
   }
}
else
{
   Debug.Log("Can't find MyObject!", this ) ;
}

แหล่งที่มา:

  1. http://answers.unity3d.com/questions/47830/what-is-a-null-reference-exception-in-unity.html
  2. /programming/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it/218510#218510
  3. https://support.unity3d.com/hc/en-us/articles/206369473-NullReferenceException
  4. https://unity3d.com/fr/learn/tutorials/topics/scripting/data-types

นี่เป็นความพยายามอย่างมากในการอธิบาย "วิธีการ" ในการวินิจฉัยปัญหา ฉันไม่คิดว่าคำตอบที่แท้จริงสำหรับคำถาม " ปัญหาคืออะไร" นอกจากนี้ยังไม่สามารถตอบคำถามที่มักจะปรากฏในคำถามประเภทนี้ บางทีนี่อาจจะดีกว่าที่เอกสาร StackOverflow อาจจะไม่.
Gnemlock

2
ฉันจะไม่พูดว่าการใช้บันทึกการแก้ปัญหาคือขี้เกียจ สำหรับฉันมันเร็วกว่ามากในการใช้ debug.log เพื่อ จำกัด ขอบเขตของข้อผิดพลาดที่เกิดขึ้นจากนั้นใช้ดีบักเกอร์เพื่อค้นหาข้อผิดพลาดจริง ๆ แต่มันก็ขึ้นอยู่กับข้อผิดพลาดในมือเสมอ ไม่ว่าในกรณีใดฉันจะไม่พูดว่าใช้บันทึกการแก้ปัญหาเป็นคนขี้เกียจ : P
Vaillancourt

คุณควรจะชี้ให้เห็นด้วยว่าไม่ใช่ความคิดที่ดีที่จะนำเช็คไปเป็นโมฆะ try/catchความคิดที่เลวร้ายยิ่งที่จะใช้ ข้อผิดพลาดจะบอกคุณมากมายเกี่ยวกับปัญหาที่คุณมีและก่อนที่ผู้เริ่มต้นจะเริ่มตรวจสอบศูนย์ทุกที่คุณมีปัญหาหลักคือผู้ตรวจสอบเนื่องจากคุณลืมอ้างอิงวัตถุบางอย่าง (ลากวัตถุไปยังสคริปต์) ฉันได้เห็นโค้ดจำนวนมากtry/catchและการตรวจสอบ null ในสถานที่ที่ไม่จำเป็นทั้งหมด การดีบักและทำงานกับรหัสเช่นนั้นคือ "ความเจ็บปวดใน **" ผู้เริ่มต้นเรียนรู้เกี่ยวกับกรณีการใช้งานของการตรวจสอบเหล่านั้นและจากนั้นใช้พวกเขาเท่านั้น
Candid Moon _Max_

ฉันคิดว่าการมีการตรวจสอบที่ว่างelseเปล่าอาจเป็นความคิดที่ดีถ้ามีข้อความ Debug ชัดเจนใน การมี a NullReferenceExceptionไม่ได้อธิบายตนเองเสมอในขณะที่No Rigidbody component attached to the gameObjectอธิบายสิ่งที่ผิดโดยตรง ฉันยอมรับว่าการมีif( obj != null )ข้อความที่ไม่มีข้อความเพียงแค่ "ซ่อน" ปัญหาและคุณสามารถมีโครงการที่ทำงานอยู่ แต่ไม่ทำในสิ่งที่คุณคาดหวังโดยไม่ทราบสาเหตุ
Hellium

4

ในขณะที่เราสามารถตรวจสอบได้อย่างง่ายดายเพื่อให้แน่ใจว่าเราไม่ได้พยายามเข้าถึงข้อมูลอ้างอิงที่เป็นโมฆะนี่ไม่ใช่วิธีการแก้ปัญหาที่เหมาะสมเสมอไป หลายครั้งในการเขียนโปรแกรม Unity ปัญหาของเราอาจเกิดจากข้อเท็จจริงที่ว่าการอ้างอิงไม่ควรเป็นโมฆะ ในบางสถานการณ์การเพิกเฉยการอ้างอิงแบบ null สามารถทำลายรหัสของเราได้

ตัวอย่างเช่นอาจเป็นข้อมูลอ้างอิงไปยังตัวควบคุมอินพุตของเรา มันเป็นเรื่องใหญ่ว่าเป็นเกมที่ไม่ผิดพลาดเนื่องจากการยกเว้นการอ้างอิงที่ว่าง แต่เราต้องคิดออกว่าทำไมไม่มีการควบคุมการป้อนข้อมูลและแก้ไขปัญหาที่เกิดปัญหา หากไม่มีมันเรามีเกมที่อาจไม่ผิดพลาด แต่ไม่สามารถรับข้อมูลได้เช่นกัน

ด้านล่างนี้ฉันจะแสดงเหตุผลและวิธีแก้ไขที่เป็นไปได้ขณะที่ฉันเจอพวกเขาในคำถามอื่น


คุณกำลังพยายามเข้าเรียน "ผู้จัดการ" หรือไม่?

หากพยายามที่จะเข้าถึงระดับที่ทำหน้าที่เป็น "ผู้จัดการ" (นั่นคือระดับที่ควรเท่านั้นที่เคยมีกรณีหนึ่งที่ทำงานในเวลา) คุณอาจจะดีกว่าการใช้วิธีการซิงเกิล คลาส Singleton สามารถเข้าถึงได้จากทุกที่โดยตรงโดยpublic staticอ้างอิงถึงตัวเอง ด้วยวิธีนี้ซิงเกิลสามารถมีการอ้างอิงไปยังอินสแตนซ์ที่ใช้งานอยู่ซึ่งจะสามารถเข้าถึงได้โดยไม่มีปัญหาในการตั้งค่าการอ้างอิงจริงทุกครั้ง

คุณอ้างถึงอินสแตนซ์ของวัตถุของคุณหรือไม่

เป็นเรื่องปกติที่จะทำเครื่องหมายการอ้างอิงเพียงอย่างเดียวpublicเพื่อให้เราสามารถตั้งค่าการอ้างอิงกับตัวอย่างผ่านทางผู้ตรวจสอบ โปรดตรวจสอบว่าคุณได้ตั้งค่าการอ้างอิงไปยังอินสแตนซ์ผ่านทางผู้ตรวจสอบเนื่องจากไม่ใช่เรื่องผิดปกติที่จะพลาดขั้นตอนนี้

คุณยกตัวอย่างของคุณหรือไม่?

ถ้าเรากำลังตั้งค่าวัตถุของเราในรหัสมันเป็นสิ่งสำคัญเพื่อให้แน่ใจว่าเรายกตัวอย่างวัตถุ สิ่งนี้สามารถทำได้โดยใช้newคำหลักและวิธีการสร้าง ตัวอย่างเช่นพิจารณาสิ่งต่อไปนี้:

private GameObject gameObject;

เราได้สร้างการอ้างอิงถึง a GameObjectแต่มันไม่ได้ชี้ไปที่อะไรเลย การเข้าถึงเอกสารอ้างอิงนี้ตามที่เป็นอยู่จะส่งผลให้ยกเว้นการอ้างอิงที่ว่าง ก่อนที่เราจะอ้างอิงGameObjectอินสแตนซ์ของเราเราอาจเรียกวิธีการกำหนดค่าเริ่มต้นดังนี้:

gameObject = new GameObject();

บทช่วยสอน Unity ในชั้นเรียนอธิบายวิธีปฏิบัติในการสร้างและใช้งานตัวสร้าง

คุณใช้GetComponent<t>()วิธีที่มีข้อสันนิษฐานว่ามีองค์ประกอบอยู่หรือไม่?

ขั้นแรกตรวจสอบให้แน่ใจว่าเราเรียกเสมอGetComponent<t>()ก่อนที่เราจะเรียกวิธีการจากอินสแตนซ์องค์ประกอบ

GetComponent<t>()สำหรับเหตุผลที่ไม่คุ้มค่าที่จะไปในที่เราอาจคิดวัตถุเกมในท้องถิ่นของเรามีส่วนหนึ่งและพยายามที่จะเข้าถึงได้ด้วย ถ้าวัตถุเกมในประเทศไม่ได้มีองค์ประกอบที่โดยเฉพาะอย่างยิ่งที่เราจะกลับnullค่า

คุณสามารถตรวจสอบได้อย่างง่ายดายว่าค่าที่ส่งคืนคือnullก่อนที่จะเข้าถึงหรือไม่ อย่างไรก็ตามถ้าวัตถุเกมของคุณควรมีองค์ประกอบที่จำเป็นมันอาจจะดีกว่าเพื่อให้แน่ใจว่าอย่างน้อยก็มีส่วนประกอบเริ่มต้นเป็นส่วนประกอบนั้น เราสามารถติดแท็กMonoBehaviourเป็น[RequireComponent(typeof(t))]เพื่อให้แน่ใจว่าเรามักจะมีประเภทขององค์ประกอบที่

นี่คือตัวอย่างของหนึ่งสำหรับวัตถุที่เกมที่ควรมีMonoBehaviour Rigidbodyถ้าสคริปต์จะถูกเพิ่มไปยังวัตถุที่เกมที่ไม่ได้มีRigidbodyการเริ่มต้นRigidbodyจะถูกสร้างขึ้น

[RequireComponent(typeof(Rigidbody))]
public class AlwaysHasRigidbody : MonoBehaviour
{
    Rigidbody myRigidbody;


    void Start()
    {
        myRigidbody = GetComponent<Rigidbody>();
    }
}

คุณพยายามสร้างโครงการของคุณใหม่หรือไม่?

มีบางกรณีที่ Unity อาจทำให้เกิดปัญหาโดยพยายามอ้างอิงวัตถุเกมที่แคชไว้ สอดคล้องกับอายุ "ปิดและเปิดอีกครั้ง" โซลูชันลองลบโฟลเดอร์Libraryของคุณและเปิด Unity อีกครั้ง ความสามัคคีจะถูกบังคับให้สร้างโครงการของคุณอีกครั้ง สิ่งนี้สามารถแก้ไขอินสแตนซ์ที่แปลกประหลาดบางอย่างของปัญหานี้และควรชี้ไปที่ปัญหาที่จะไม่เกิดขึ้นในโครงสร้างขั้นสุดท้าย


1
ฉันยังไม่แน่ใจว่าคำถามนี้ควรอยู่ในหัวข้อหรือไม่ แต่นี่คือวิกิชุมชนสำหรับผู้ใช้ในการโพสต์คำตอบที่เป็นไปได้เพิ่มเติม จนถึงตอนนี้มันประกอบด้วยพื้นฐานของหน้าครึ่งแรกของคำตอบที่ยอมรับสำหรับคำถามที่ทำเครื่องหมายว่ามีความเป็นเอกภาพและ"การอ้างอิงโมฆะ" (ที่ตรงตามเกณฑ์ของคำถาม)
Gnemlock

-5

ฉันเห็นว่ามีคำตอบที่ยอมรับได้ NullReferenceExceptionแต่มีคำตอบที่ดีหรือข้อเสนอแนะสำหรับคุณสำหรับการจัดการ หากคุณสามารถเกี่ยวข้องกับการเขียนโปรแกรมในภาษาจาวาเช่นฉันคุณสามารถป้องกันไม่ให้ส่งข้อผิดพลาดเป็นโมฆะโดยใช้try-catchบล็อก ลองด้วยตัวคุณเอง! ;-)

หากคุณใช้งานใน C # ให้ตรวจสอบว่าคุณมีusing System;ไฟล์สคริปต์ที่ด้านบนหรือไม่ ถ้าไม่เพิ่มให้ ตอนนี้คุณสามารถใช้Exceptionคลาสได้ทุกประเภทในขณะที่พยายามจับคู่โค้ด

หากคุณกำลังใช้ UnityScript ให้ใช้ import System;

นี่คือตัวอย่าง:

using System; // --> This exact line of code. That's it.
using UnityEngine;

public class Test : MonoBehaviour {

    public GameObject player; // --> Example to check if there's a null content;

    public void Update() {

        // You may now catch null reference here.
        try {

            player.transform.Translate(0, 0, 2);

        } catch(NullReferenceException e) { // --> You may use this type of exception class

        }

    }
}

ยังจำได้คุณสามารถจับข้อยกเว้นนอกจากนี้ยังมีอื่น ๆ เช่นMissingReferenceException, MissingComponentException, IndexOutOfRangeExceptionหรือการเรียนยกเว้นอื่น ๆ ตราบใดที่คุณใส่using Systemในสคริปต์ของคุณ

นั้นคือทั้งหมด.


2
วิธีการลองและจับได้รับการอธิบายในคำตอบที่ยอมรับ ....
Hellium
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.