ทำไมเราถึงได้เป็นไปได้ dereference คำเตือนอ้างอิงโมฆะเมื่อการอ้างอิงโมฆะดูเหมือนจะเป็นไปไม่ได้?


9

เมื่ออ่านคำถามนี้ทาง HNQ ฉันได้อ่านเกี่ยวกับประเภทข้อมูลอ้างอิงที่น่าเบื่อใน C # 8และทำการทดลองบางอย่าง

ฉันรู้ว่า 9 ครั้งใน 10 ครั้งหรือบ่อยกว่านั้นเมื่อมีคนพูดว่า "ฉันพบข้อผิดพลาดในการคอมไพเลอร์!" นี่คือการออกแบบและความเข้าใจผิดของพวกเขาเอง และตั้งแต่ฉันเริ่มมองหาคุณลักษณะนี้เฉพาะในวันนี้อย่างชัดเจนฉันไม่เข้าใจมันมากนัก ด้วยวิธีนี้ให้ดูที่รหัสนี้:

#nullable enable
class Program
{
    static void Main()
    {
        var s = "";
        var b = s == null; // If you comment this line out, the warning on the line below disappears
        var i = s.Length; // warning CS8602: Dereference of a possibly null reference
    }
}

หลังจากอ่านเอกสารที่ฉันเชื่อมโยงไปด้านบนฉันคาดว่าs == nullบรรทัดที่จะเตือนฉัน - หลังจากทั้งหมดsเห็นได้ชัดว่าไม่เป็นโมฆะดังนั้นการเปรียบเทียบมันnullจึงไม่สมเหตุสมผล

แต่ฉันได้รับคำเตือนในบรรทัดถัดไปและคำเตือนบอกว่าsเป็นไปได้ที่จะมีการอ้างอิงที่เป็นโมฆะแม้ว่าสำหรับมนุษย์แล้วจะเห็นได้ชัดว่าไม่ใช่

มากกว่าคำเตือนจะไม่ปรากฏขึ้นถ้าเราไม่ได้เปรียบเทียบไปsnull

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

แทนที่จะสร้างปัญหา GitHub ใหม่และอาจสละเวลาของผู้มีส่วนร่วมในโครงการที่ยุ่งอย่างไม่น่าเชื่อฉันกำลังนำเรื่องนี้ไปสู่ชุมชน

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


ดูเหมือนว่าคอมไพเลอร์เองตั้งพฤติกรรมที่ ณ จุดนี้ตัวแปร "s" อาจเป็นโมฆะ อย่างไรก็ตามถ้าฉันเคยใช้สตริงหรือวัตถุดังนั้นควรมีการตรวจสอบก่อนที่คุณจะเรียกใช้ฟังก์ชัน "s? .Length" ควรทำเคล็ดลับและคำเตือนควรหายไป
CHG

1
@chg ไม่จำเป็นต้องมี?เพราะsไม่เป็นโมฆะ มันไม่ได้กลายเป็น nullable nullเพียงเพราะเราก็พอโง่ที่จะเปรียบเทียบกับ
Andrew Savinykh

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

@ Stuartd ใช่นั่นคือสิ่งที่ดูเหมือนว่ามันเป็น ดังนั้นคำถามคือทำไมจึงมีประโยชน์
Andrew Savinykh

1
@chg นั่นคือสิ่งที่ฉันพูดในเนื้อหาของคำถามใช่ไหม?
Andrew Savinykh

คำตอบ:


7

นี่เป็นคำตอบที่ซ้ำกันที่ @stuartd เชื่อมโยงได้อย่างมีประสิทธิภาพดังนั้นฉันจะไม่เข้าไปดูรายละเอียดที่นี่ แต่รากของเรื่องคือว่านี่ไม่ใช่ข้อผิดพลาดทางภาษาหรือข้อผิดพลาดของคอมไพเลอร์ แต่มันเป็นพฤติกรรมที่ตั้งใจตามที่ใช้ เราติดตามสถานะโมฆะของตัวแปร เมื่อคุณเริ่มประกาศตัวแปรสถานะนั้นเป็น NotNull เนื่องจากคุณกำหนดค่าเริ่มต้นอย่างชัดเจนด้วยค่าที่ไม่ใช่ null แต่เราไม่ได้ติดตามว่า NotNull นั้นมาจากไหน ตัวอย่างเช่นนี่คือรหัสที่เทียบเท่าได้อย่างมีประสิทธิภาพ:

#nullable enable
class Program
{
    static void Main()
    {
        M("");
    }
    static void M(string s)
    {
        var b = s == null;
        var i = s.Length; // warning CS8602: Dereference of a possibly null reference
    }
}

ในทั้งสองกรณีคุณอย่างชัดเจนทดสอบสำหรับs nullเราใช้เวลานี้เป็น input เพื่อการวิเคราะห์การไหลเช่นเดียวกับ Mads ตอบในคำถามนี้: https://stackoverflow.com/a/59328672/2672518 ในคำตอบนั้นผลลัพธ์คือคุณได้รับคำเตือนเรื่องการคืนสินค้า ในกรณีนี้คำตอบคือคุณได้รับคำเตือนว่าคุณถูกอ้างอิงเป็นโมฆะ

มันไม่ได้กลายเป็น nullable nullเพียงเพราะเราก็พอโง่ที่จะเปรียบเทียบกับ

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


ขอบคุณ. ส่วนใหญ่จะเน้นความคล้ายคลึงกับคำถามอื่น ๆ ฉันต้องการที่จะแก้ไขความแตกต่างให้มากขึ้น ตัวอย่างเช่นทำไมs == nullไม่สร้างคำเตือน?
Andrew Savinykh

นอกจากนี้ฉันเพิ่งรู้ว่าด้วย#nullable enable; string s = "";s = null;รวบรวมและทำงาน (มันยังคงสร้างคำเตือน) สิ่งที่เป็นประโยชน์ของการดำเนินงานที่ช่วยให้การกำหนดเป็นโมฆะ "การอ้างอิงที่ไม่ใช่โมฆะ" ในบริบทการเปิดใช้งานคำอธิบายประกอบ null?
Andrew Savinykh

คำตอบของ Mad คลั่งไปที่ข้อเท็จจริงที่ว่า "[คอมไพเลอร์] ไม่ได้ติดตามความสัมพันธ์ระหว่างสถานะของตัวแปรที่แยกจากกัน" เราไม่มีตัวแปรแยกในตัวอย่างนี้ดังนั้นฉันจึงมีปัญหาในการใช้คำตอบที่เหลือของ Mad ในกรณีนี้
Andrew Savinykh

มันไปโดยไม่พูด แต่จำฉันอยู่ที่นี่ไม่ได้วิจารณ์ แต่เพื่อเรียนรู้ ฉันใช้ C # ตั้งแต่ครั้งแรกที่มันออกมาในปี 2001 แม้ว่าฉันจะไม่ได้เป็นคนใหม่ในภาษา เป้าหมายของคำถามนี้คือเพื่อให้เหตุผลที่ว่าทำไมพฤติกรรมนี้มีประโยชน์สำหรับมนุษย์
Andrew Savinykh

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

0

หากการอนุมานค่าความไม่เป็นโมฆะไม่ใช่การพิสูจน์ด้วยกระสุน [.. ] จะเกิดความผิดพลาดได้อย่างไร?

ฉันมีความสุขที่ได้นำการอ้างอิงที่เป็นโมฆะของ C # 8 มาใช้ได้อย่างมีความสุขทันทีที่มี เมื่อฉันคุ้นเคยกับการใช้สัญลักษณ์ [NotNull] (ฯลฯ ) ของ ReSharper ฉันจึงสังเกตเห็นความแตกต่างระหว่างทั้งสอง

คอมไพเลอร์ C # สามารถหลอกได้ แต่มีแนวโน้มที่จะผิดพลาดในด้านของความระมัดระวัง (โดยปกติไม่เสมอไป)

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

  • null null มักใช้เพื่อหลีกเลี่ยงการเตือนการถูกอ้างถึง แต่ทำให้วัตถุไม่เป็นโมฆะ ดูเหมือนว่าคุณต้องการเก็บรองเท้าของคุณไว้ในสองรองเท้า
    string s = null!; //No warning

  • การวิเคราะห์พื้นผิว ในทางตรงกันข้ามกับ ReSharper (ซึ่งใช้การเพิ่มความคิดเห็นด้วยรหัส ) คอมไพเลอร์ C # ยังคงไม่สนับสนุนคุณลักษณะทั้งหมดเพื่อจัดการกับการอ้างอิงที่ไม่มีค่า
    void DoSomethingWith(string? s)
    {    
        ThrowIfNull(s);
        var split = s.Split(' '); //Dereference warning
    }

แม้ว่าจะอนุญาตให้ใช้โครงสร้างบางอย่างเพื่อตรวจสอบ nullability ที่กำจัดคำเตือน:

    public static void DoSomethingWith(string? s)
    {
        Debug.Assert(s != null, nameof(s) + " != null");
        var split = s.Split(' ');  //No warning
    }

หรือ (ยังคงคุณสมบัติเด็ด) (หาได้ทั้งหมดที่นี่ ):

    public static bool IsNullOrEmpty([NotNullWhen(false)] string? value)
    {
        ...
    }

  • การวิเคราะห์รหัสที่ไวต่อการสัมผัส นี่คือสิ่งที่คุณนำมาสู่แสงสว่าง คอมไพเลอร์จำเป็นต้องตั้งสมมติฐานเพื่อให้สามารถใช้งานได้และบางครั้งอาจดูเหมือนเป็นการต่อต้าน (สำหรับมนุษย์อย่างน้อย)
    void DoSomethingWith(string s)
    {    
        var b = s == null;
        var i = s.Length; // Dereference warning
    }

  • ปัญหากับยาชื่อสามัญ ถามที่นี่และอธิบายได้ดีมากที่นี่ (บทความเดียวกับก่อนหน้าย่อหน้า "ปัญหากับ T?") ยาสามัญมีความซับซ้อนเนื่องจากต้องทำให้ทั้งการอ้างอิงและค่านิยมมีความสุข ความแตกต่างที่สำคัญคือในขณะที่string?เป็นเพียงแค่สตริงint?กลายเป็นNullable<int>และบังคับให้คอมไพเลอร์จัดการกับมันในวิธีที่แตกต่างกันอย่างมาก นอกจากนี้ที่นี่คอมไพเลอร์กำลังเลือกเส้นทางที่ปลอดภัยบังคับให้คุณระบุสิ่งที่คุณคาดหวัง:
    public interface IResult<out T> : IResult
    {
        T? Data { get; } //Warning/Error: A nullable type parameter must be known to be a value type or non-nullable reference type.
    }

แก้ไขข้อ จำกัด ให้:

    public interface IResult<out T> : IResult where T : class { T? Data { get; }}
    public interface IResult<T> : IResult where T : struct { T? Data { get; }}

แต่ถ้าเราไม่ใช้ข้อ จำกัด และลบ '?' จากข้อมูลเรายังสามารถใส่ค่า Null ในนั้นโดยใช้คำหลัก 'เริ่มต้น':

    [Pure]
    public static Result<T> Failure(string description, T data = default)
        => new Result<T>(ResultOutcome.Failure, data, description); 
        // data here is definitely null. No warning though.

อันสุดท้ายดูเหมือนจะมีเล่ห์เหลี่ยมกับฉันเพราะมันอนุญาตให้เขียนรหัสที่ไม่ปลอดภัย

หวังว่านี่จะช่วยใครซักคน


ผมขอแนะนำให้เอกสารใน nullable แอตทริบิวต์การอ่าน: docs.microsoft.com/en-us/dotnet/csharp/nullable-attributes พวกเขาจะแก้ปัญหาของคุณโดยเฉพาะในส่วนการวิเคราะห์พื้นผิวและข้อมูลทั่วไป
333fred

ขอบคุณสำหรับลิงค์ @ 333fred แม้ว่าจะมีคุณลักษณะที่คุณสามารถเล่นได้ แต่คุณลักษณะที่แก้ปัญหาที่ฉันโพสต์ (สิ่งที่บอกฉันว่าThrowIfNull(s);เชื่อฉันว่าsไม่เป็นโมฆะ) ไม่มีอยู่ นอกจากนี้บทความอธิบายถึงวิธีการไปยังตัวจัดการไม่ใช่ nullableยาชื่อสามัญในขณะที่ผมแสดงให้เห็นว่าคุณสามารถ "หลอก" คอมไพเลอร์ที่มีค่า Null แต่คำเตือนเกี่ยวกับมันไม่
Alvin Sartor

ที่จริงแล้วแอ็ตทริบิวต์นั้นมีอยู่จริง ฉันยื่นข้อผิดพลาดในเอกสารเพื่อเพิ่ม DoesNotReturnIf(bool)คุณกำลังมองหา
333fred

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