เหตุใดรหัสนี้จึงให้คำเตือน“ การอ้างอิงโมฆะการอ้างอิงที่เป็นไปได้ที่เป็นไปได้”


70

พิจารณารหัสต่อไปนี้:

using System;

#nullable enable

namespace Demo
{
    public sealed class TestClass
    {
        public string Test()
        {
            bool isNull = _test == null;

            if (isNull)
                return "";
            else
                return _test; // !!!
        }

        readonly string _test = "";
    }
}

เมื่อผมสร้างนี้เส้นที่มีเครื่องหมายให้เตือนเรียบเรียง:!!!warning CS8603: Possible null reference return.

ฉันพบว่ามันสับสนเล็กน้อยซึ่ง_testอ่านได้อย่างเดียวและเริ่มต้นว่าไม่ใช่ค่าว่าง

หากฉันเปลี่ยนรหัสเป็นสิ่งต่อไปนี้คำเตือนจะหายไป:

        public string Test()
        {
            // bool isNull = _test == null;

            if (_test == null)
                return "";
            else
                return _test;
        }

มีใครอธิบายพฤติกรรมนี้ได้บ้าง


1
Debug.Assert ไม่เกี่ยวข้องเนื่องจากเป็นการตรวจสอบขณะใช้งานในขณะที่คำเตือนคอมไพเลอร์เป็นการตรวจสอบเวลาที่คอมไพล์ คอมไพเลอร์ไม่มีสิทธิ์เข้าถึงลักษณะการทำงานขณะใช้งานจริง
Polyfun

5
The Debug.Assert is irrelevant because that is a runtime check- มีความเกี่ยวข้องเพราะหากคุณแสดงความคิดเห็นในบรรทัดนั้นคำเตือนจะหายไป
Matthew Watson

1
@Polyfun: คอมไพเลอร์อาจรู้ (ผ่านคุณสมบัติ) ที่Debug.Assertจะโยนข้อยกเว้นหากการทดสอบล้มเหลว
Jon Skeet

2
ฉันได้เพิ่มกรณีต่าง ๆ มากมายที่นี่และมีผลลัพธ์ที่น่าสนใจจริงๆ จะเขียนคำตอบในภายหลัง - ทำงานเพื่อทำตอนนี้
Jon Skeet

2
@EricLippert: Debug.Assertตอนนี้มีคำอธิบายประกอบ ( src ) ของDoesNotReturnIf(false)สำหรับพารามิเตอร์เงื่อนไข
Jon Skeet

คำตอบ:


38

การวิเคราะห์การไหลของ nullable ติดตามสถานะของตัวแปรที่ว่างเปล่า แต่มันจะไม่ติดตามสถานะอื่นเช่นค่าของboolตัวแปร (ดังกล่าวisNullข้างต้น) และจะไม่ติดตามความสัมพันธ์ระหว่างสถานะของตัวแปรที่แยกต่างหาก (เช่นisNullและ_test)

เครื่องมือวิเคราะห์สแตติกที่เกิดขึ้นจริงอาจจะทำสิ่งเหล่านั้น แต่ก็จะเป็น "ฮิวริสติก" หรือ "ตามอำเภอใจ" ในระดับหนึ่ง: คุณไม่สามารถบอกกฎที่มันกำลังติดตามได้และกฎเหล่านั้นอาจเปลี่ยนแปลงตลอดเวลา

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

ในขณะที่เราเปิดตัวฟีเจอร์มันให้ความรู้สึกเหมือนว่าเราได้รับความสมดุลอย่างมาก แต่ก็มีบางสถานที่ที่เกิดขึ้นอย่างน่าอึดอัดใจและเราจะกลับไปที่ C # 9.0 อีกครั้ง


3
คุณรู้ว่าคุณต้องการใส่ทฤษฎีขัดแตะลงในสเปค; ทฤษฎีโครงข่ายน่ากลัวและไม่สับสนเลย! ทำมัน! :)
Eric Lippert

7
คุณรู้ว่าคำถามของคุณนั้นถูกต้องเมื่อผู้จัดการโปรแกรมสำหรับ C # ตอบกลับ!
Sam Rueby

1
@ TanveerBadar: ทฤษฎี Lattice เป็นเรื่องเกี่ยวกับการวิเคราะห์ชุดของค่าที่มีลำดับบางส่วน; ประเภทเป็นตัวอย่างที่ดี ถ้าค่าของประเภท X สามารถกำหนดให้กับตัวแปรประเภท Y ได้นั่นก็หมายความว่า Y คือ "ใหญ่พอ" ที่จะถือ X และนั่นก็เพียงพอที่จะสร้างโครงตาข่ายซึ่งจากนั้นบอกเราว่าการตรวจสอบความสามารถในการมอบหมายในคอมไพเลอร์ ในสเปคในแง่ของทฤษฎีขัดแตะ สิ่งนี้เกี่ยวข้องกับการวิเคราะห์แบบสแตติกเนื่องจากหัวข้อที่น่าสนใจมากมายสำหรับนักวิเคราะห์นอกเหนือจากความสามารถในการกำหนดประเภทยังสามารถแสดงได้ในแง่ของโปรย
Eric Lippert

1
@TanveerBadar: lara.epfl.ch/w/_media/sav08:schwartzbach.pdfมีตัวอย่างเบื้องต้นที่ดีเกี่ยวกับวิธีที่เครื่องมือวิเคราะห์แบบคงที่ใช้ทฤษฎีขัดแตะ
Eric Lippert

1
@EricLippert Awesome ไม่เริ่มอธิบายคุณ ลิงค์นั้นกำลังจะเข้าสู่รายการที่ต้องอ่านของฉันทันที
Tanveer Badar

56

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

คำตอบนี้อยู่ในรูปแบบการเล่าเรื่องค่อนข้างมากกว่า "นี่คือข้อสรุป" ... ฉันหวังว่ามันจะมีประโยชน์มากกว่านี้

ฉันจะทำให้ตัวอย่างง่ายขึ้นเล็กน้อยโดยกำจัดฟิลด์และพิจารณาเมธอดที่มีหนึ่งในสองลายเซ็นต์:

public static string M(string? text)
public static string M(string text)

ในการใช้งานด้านล่างฉันได้กำหนดหมายเลขที่แตกต่างกันในแต่ละวิธีเพื่อให้ฉันสามารถอ้างอิงตัวอย่างที่ชัดเจนได้อย่างไม่น่าสงสัย นอกจากนี้ยังช่วยให้การใช้งานทั้งหมดจะปรากฏในโปรแกรมเดียวกัน

ในแต่ละกรณีที่อธิบายไว้ด้านล่างเราจะทำสิ่งต่าง ๆ แต่จบลงด้วยความพยายามที่จะกลับมาtext- ดังนั้นจึงเป็นสถานะว่างเปล่าtextเปล่าที่สำคัญ

ผลตอบแทนที่ไม่มีเงื่อนไข

อันดับแรกให้ลองส่งคืนโดยตรง:

public static string M1(string? text) => text; // Warning
public static string M2(string text) => text;  // No warning

จนถึงตอนนี้ง่ายมาก สถานะที่เป็นโมฆะของพารามิเตอร์ที่จุดเริ่มต้นของวิธีการคือ "อาจเป็นโมฆะ" หากเป็นประเภทstring?และ "ไม่เป็นโมฆะ" หากเป็นประเภทstringถ้าหากมันเป็นประเภท

ผลตอบแทนตามเงื่อนไขง่าย ๆ

ตอนนี้เรามาตรวจสอบ null ภายในifเงื่อนไข statement (ฉันจะใช้โอเปอเรเตอร์ที่มีเงื่อนไขซึ่งฉันเชื่อว่าจะมีผลเหมือนกัน แต่ฉันอยากอยู่กับคำถามที่แท้จริงยิ่งขึ้น)

public static string M3(string? text)
{
    if (text is null)
    {
        return "";
    }
    else
    {
        return text; // No warning
    }
}

public static string M4(string text)
{
    if (text is null)
    {
        return "";
    }
    else
    {
        return text; // No warning
    }
}

ยอดเยี่ยมดังนั้นดูเหมือนว่าภายในifข้อความที่เงื่อนไขตรวจสอบความว่างเปล่าสถานะของตัวแปรภายในแต่ละสาขาของifคำสั่งอาจแตกต่างกัน: ภายในelseบล็อกสถานะเป็น "ไม่เป็นโมฆะ" ในโค้ดทั้งสองชิ้น ดังนั้นโดยเฉพาะอย่างยิ่งใน M3 สถานะจะเปลี่ยนจาก "อาจเป็นโมฆะ" เป็น "ไม่เป็นโมฆะ"

การส่งคืนแบบมีเงื่อนไขพร้อมตัวแปรโลคัล

ทีนี้ลองยกเงื่อนไขนั้นไปที่ตัวแปรท้องถิ่น:

public static string M5(string? text)
{
    bool isNull = text is null;
    if (isNull)
    {
        return "";
    }
    else
    {
        return text; // Warning
    }
}

public static string M6(string text)
{
    bool isNull = text is null;
    if (isNull)
    {
        return "";
    }
    else
    {
        return text; // Warning
    }
}

คำเตือนปัญหาทั้ง M5 และ M6 ดังนั้นไม่เพียง แต่เราจะไม่ได้รับผลกระทบเชิงบวกจากการเปลี่ยนสถานะจาก "อาจเป็นโมฆะ" เป็น "ไม่เป็นโมฆะ" ใน M5 (เช่นเดียวกับใน M3) ... เราได้ตรงกันข้ามผลใน M6 ซึ่งรัฐเปลี่ยนไป " ไม่ใช่โมฆะ "เป็น" อาจเป็นโมฆะ " นั่นทำให้ฉันประหลาดใจจริงๆ

ดังนั้นดูเหมือนว่าเราได้เรียนรู้ว่า:

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

ผลตอบแทนที่ไม่มีเงื่อนไขหลังจากการเปรียบเทียบที่ถูกละเว้น

ลองดูที่สองของสัญลักษณ์แสดงหัวข้อย่อยเหล่านั้นโดยการแนะนำการเปรียบเทียบก่อนที่จะกลับมาอย่างไม่มีเงื่อนไข (ดังนั้นเราจึงไม่สนใจผลการเปรียบเทียบอย่างสมบูรณ์):

public static string M7(string? text)
{
    bool ignored = text is null;
    return text; // Warning
}

public static string M8(string text)
{
    bool ignored = text is null;
    return text; // Warning
}

โปรดสังเกตว่า M8 รู้สึกอย่างไรว่าควรเทียบเท่ากับ M2 - ทั้งคู่มีพารามิเตอร์ที่ไม่เป็นโมฆะซึ่งพวกเขากลับมาโดยไม่มีเงื่อนไข - แต่การแนะนำการเปรียบเทียบด้วย null จะเปลี่ยนสถานะจาก "ไม่ใช่โมฆะ" เป็น "อาจเป็นโมฆะ" เราสามารถรับหลักฐานเพิ่มเติมเกี่ยวกับสิ่งนี้ได้โดยพยายามอ้างเหตุผลtextก่อนเงื่อนไข:

public static string M9(string text)
{
    int length1 = text.Length;   // No warning
    bool ignored = text is null;
    int length2 = text.Length;   // Warning
    return text;                 // No warning
}

โปรดสังเกตว่าreturnคำสั่งไม่มีคำเตือนในขณะนี้: สถานะหลังจากดำเนินการtext.Lengthคือ "ไม่เป็นโมฆะ" (เพราะถ้าเราดำเนินการนิพจน์นั้นสำเร็จจะไม่เป็นโมฆะ) ดังนั้นtextพารามิเตอร์เริ่มต้นเป็น "ไม่เป็นโมฆะ" เนื่องจากชนิดของมันกลายเป็น "อาจเป็นโมฆะ" เนื่องจากการเปรียบเทียบเป็นโมฆะจากนั้นกลายเป็น "ไม่เป็นโมฆะ" อีกครั้งหลังจากtext2.Lengthนั้น

การเปรียบเทียบอะไรส่งผลกระทบต่อรัฐ

นั่นคือการเปรียบเทียบtext is null... การเปรียบเทียบที่คล้ายกันมีผลอย่างไร ต่อไปนี้เป็นวิธีการสี่วิธีทั้งหมดเริ่มต้นด้วยพารามิเตอร์สตริงที่ไม่ใช่ค่า null:

public static string M10(string text)
{
    bool ignored = text == null;
    return text; // Warning
}

public static string M11(string text)
{
    bool ignored = text is object;
    return text; // No warning
}

public static string M12(string text)
{
    bool ignored = text is { };
    return text; // No warning
}

public static string M13(string text)
{
    bool ignored = text != null;
    return text; // Warning
}

ดังนั้นแม้ว่าx is objectในขณะนี้คือทางเลือกที่แนะนำให้x != nullพวกเขาไม่ได้มีผลเช่นเดียวกันเพียง แต่เปรียบเทียบกับ null (ใด ๆis, ==หรือ!=) เปลี่ยนสถานะจาก "ไม่เป็นโมฆะ" กับ "บางที null"

เหตุใดการยกเงื่อนไขจึงมีผล

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

public static int X1()
{
    if (true)
    {
        return 1;
    }
}

public static int X2()
{
    bool alwaysTrue = true;
    if (alwaysTrue)
    {
        return 1;
    }
    // Error: not all code paths return a value
}

แม้ว่าเราจะรู้ว่ามันalwaysTrueจะเป็นจริงเสมอ แต่ก็ไม่เป็นไปตามข้อกำหนดในสเปคที่ทำให้โค้ดหลังจากifคำสั่งไม่สามารถเข้าถึงได้ซึ่งเป็นสิ่งที่เราต้องการ

นี่เป็นอีกตัวอย่างหนึ่งที่เกี่ยวข้องกับการมอบหมายที่ชัดเจน:

public static void X3()
{
    string x;
    bool condition = DateTime.UtcNow.Year == 2020;
    if (condition)
    {
        x = "It's 2020.";
    }
    if (!condition)
    {
        x = "It's not 2020.";
    }
    // Error: x is not definitely assigned
    Console.WriteLine(x);
}

แม้ว่าเรารู้ว่ารหัสจะเข้าสู่หนึ่งในเนื้อหาของifคำสั่งเหล่านั้นอย่างแน่นอนแต่ก็ไม่มีอะไรพิเศษในการทำงาน เครื่องมือวิเคราะห์แบบคงที่อาจทำเช่นนั้นได้ แต่การพยายามใส่ลงไปในข้อกำหนดภาษาจะเป็นความคิดที่ไม่ดี IMO - เป็นเรื่องดีสำหรับเครื่องมือวิเคราะห์แบบคงที่ที่จะมีการวิเคราะห์พฤติกรรมทุกประเภทที่สามารถพัฒนาได้ตลอดเวลา แต่ไม่มากนัก สำหรับข้อกำหนดภาษา


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

6
เมื่อเราเห็นตัวอย่างif (x != null) x.foo(); x.bar();เรามีหลักฐานสองชิ้น ifคำสั่งเป็นหลักฐานของเรื่อง "ผู้เขียนเชื่อว่า x อาจเป็นโมฆะก่อนที่จะเรียกร้องให้ foo" และคำสั่งต่อไปนี้เป็นหลักฐานของ "ผู้เขียนเชื่อ x คือไม่เป็นโมฆะก่อนที่จะเรียกร้องให้บาร์" และความขัดแย้งนี้นำไปสู่การ สรุปว่ามีข้อผิดพลาด ข้อผิดพลาดเป็นทั้งข้อบกพร่องที่ค่อนข้างอ่อนโยนของการตรวจสอบโมฆะที่ไม่จำเป็นหรือข้อผิดพลาดที่อาจเกิดขึ้น ข้อผิดพลาดใดที่เป็นข้อผิดพลาดจริงไม่ชัดเจน แต่ชัดเจนว่ามีข้อผิดพลาดอยู่
Eric Lippert

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

3
ความไม่สอดคล้องกันระหว่าง "is object", "is {}" และ "! = null" เป็นรายการที่เราได้พูดคุยกันภายในสองสามสัปดาห์ที่ผ่านมา จะนำมันขึ้นมาที่ LDM ในอนาคตอันใกล้นี้เพื่อตัดสินใจว่าเราจำเป็นต้องพิจารณาสิ่งเหล่านี้ว่าเป็นการตรวจสอบโมฆะจริงหรือไม่ (ซึ่งทำให้พฤติกรรมสอดคล้องกัน)
JaredPar

1
@ArnonAxelrod ที่บอกว่ามันไม่ได้หมายความว่าเป็นโมฆะ มันอาจจะยังคงเป็นโมฆะเพราะประเภทอ้างอิง nullable เป็นเพียงคำแนะนำของคอมไพเลอร์ (ตัวอย่าง: M8 (null!) หรือโทรจากรหัส C # 7 หรือเพิกเฉยต่อคำเตือน) มันไม่เหมือนกับความปลอดภัยประเภทของส่วนที่เหลือของแพลตฟอร์ม
Jon Skeet

29

คุณได้ค้นพบหลักฐานว่าอัลกอริทึมการไหลเวียนของโปรแกรมที่สร้างคำเตือนนี้ค่อนข้างไม่ซับซ้อนเมื่อมันมาถึงการติดตามความหมายที่เข้ารหัสในตัวแปรท้องถิ่น

ฉันไม่มีความรู้เฉพาะเกี่ยวกับการใช้งานเครื่องมือตรวจสอบการไหล แต่เมื่อทำงานกับการติดตั้งโค้ดที่คล้ายกันในอดีตฉันสามารถคาดเดาการศึกษาได้ เครื่องมือตรวจสอบการไหลน่าจะหักล้างสองสิ่งในกรณีที่เป็นเท็จ: (1) _testอาจเป็นโมฆะเพราะหากไม่สามารถทำได้คุณจะไม่มีการเปรียบเทียบในตอนแรกและ (2) isNullอาจเป็นจริงหรือเท็จ - เพราะ ifถ้ามันไม่สามารถที่คุณจะไม่ได้อยู่ใน แต่การเชื่อมต่อที่return _test;รันเพียงอย่างเดียวหาก_testไม่เป็นโมฆะการเชื่อมต่อนั้นไม่ได้เกิดขึ้น

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

นอกจากนี้ตัวตรวจสอบความครอบคลุมได้รับการออกแบบให้ทำงานบนฐานรหัสขนาดใหญ่ ค้างคืน ; การวิเคราะห์คอมไพเลอร์ C # จะต้องทำงานระหว่างการกดแป้นในตัวแก้ไขซึ่งจะเปลี่ยนการวิเคราะห์เชิงลึกหลายประเภทที่คุณสามารถทำได้อย่างมีเหตุผล


"ตรงไปตรงมา" ถูกต้อง - ฉันคิดว่ามันอภัยถ้ามันสะดุดในสิ่งที่ต้องการเงื่อนไขที่เราทุกคนรู้ว่าลังเลปัญหาเป็นบิตของถั่วยากในเรื่องดังกล่าว แต่ความจริงที่ว่ามีความแตกต่างในทุกระหว่างbool b = x != nullVS bool b = x is { }(กับ ไม่ได้ใช้การมอบหมายจริง!) แสดงให้เห็นว่าแม้กระทั่งรูปแบบที่รู้จักสำหรับการตรวจสอบโมฆะก็เป็นที่น่าสงสัย ที่จะไม่ดูถูกการทำงานหนักอย่างไม่ต้องสงสัยของทีมเพื่อทำให้งานนี้เป็นส่วนใหญ่เท่าที่ควรสำหรับฐานรหัสจริงในการใช้งาน - ดูเหมือนว่าการวิเคราะห์เป็นทุน -P ในทางปฏิบัติ
Jeroen Mostert

@JeroenMostert: Jared Par กล่าวถึงความคิดเห็นในคำตอบของ Jon Skeetว่า Microsoft กำลังพูดถึงปัญหาดังกล่าวภายใน
Brian

8

คำตอบอื่น ๆ นั้นค่อนข้างถูกต้อง

ในกรณีที่มีใครอยากรู้อยากเห็นฉันพยายามสะกดตรรกะของคอมไพเลอร์อย่างชัดเจนที่สุดเท่าที่จะทำได้ https://github.com/dotnet/roslyn/issues/36927#issuecomment-508595947

ชิ้นส่วนที่ไม่ได้กล่าวถึงคือวิธีที่เราตัดสินว่าการตรวจสอบโมฆะควรพิจารณาว่าเป็น "บริสุทธิ์" ในแง่ที่ว่าถ้าคุณทำเช่นนั้นเราควรพิจารณาอย่างจริงจังว่า null นั้นเป็นไปได้หรือไม่ มีการตรวจสอบโมฆะ "บังเอิญ" จำนวนมากใน C # ซึ่งคุณทดสอบการใช้โมฆะเป็นส่วนหนึ่งของการทำอย่างอื่นดังนั้นเราจึงตัดสินใจว่าเราต้องการ จำกัด ชุดของการตรวจสอบกับคนที่เรามั่นใจว่าผู้คนตั้งใจทำ ฮิวริสติกที่เราคิดขึ้นมาคือ "ประกอบด้วยคำว่าโมฆะ" ดังนั้นนั่นคือสาเหตุx != nullและx is objectให้ผลลัพธ์ที่แตกต่างกัน

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