เหตุใดเงื่อนไข (null ||! tryParse) จึงทำให้เกิด "การใช้ตัวแปรภายในที่ไม่ได้กำหนด"


98

โค้ดต่อไปนี้ส่งผลให้ใช้ตัวแปรโลคัล "numberOfGroups" ที่ไม่ได้กำหนด :

int numberOfGroups;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

อย่างไรก็ตามรหัสนี้ใช้งานได้ดี (แม้ว่าReSharperกล่าวว่า= 10ซ้ำซ้อน):

int numberOfGroups = 10;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

ฉันขาดอะไรไปหรือคอมไพเลอร์ไม่ชอบของฉัน||?

ฉันได้ จำกัด สิ่งนี้ให้แคบลงจนdynamicทำให้เกิดปัญหา ( optionsเป็นตัวแปรแบบไดนามิกในโค้ดด้านบนของฉัน) คำถามยังคงอยู่ทำไมฉันถึงทำสิ่งนี้ไม่ได้?

รหัสนี้ไม่ได้รวบรวม:

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        dynamic myString = args[0];

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

อย่างไรก็ตามรหัสนี้ทำ :

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        var myString = args[0]; // var would be string

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

ฉันไม่รู้ว่าdynamicจะเป็นปัจจัยในเรื่องนี้


อย่าคิดว่าฉลาดพอที่จะรู้ว่าคุณไม่ได้ใช้ค่าที่ส่งผ่านไปยังoutพารามิเตอร์ของคุณเป็นอินพุต
Charleh

3
รหัสที่ให้ไว้ที่นี่ไม่ได้แสดงถึงพฤติกรรมที่อธิบายไว้ มันใช้งานได้ดี กรุณาโพสต์รหัสที่แสดงให้เห็นถึงพฤติกรรมที่คุณกำลังอธิบายซึ่งเราสามารถรวบรวมได้เอง ให้เราทั้งไฟล์
Eric Lippert

8
ตอนนี้เรามีสิ่งที่น่าสนใจ!
Eric Lippert

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

1
เมื่อมองแวบแรกดูเหมือนว่าจะมีข้อบกพร่อง
Eric Lippert

คำตอบ:


73

ฉันค่อนข้างมั่นใจว่านี่เป็นบั๊กของคอมไพเลอร์ พบดี!

แก้ไข: ไม่ใช่ข้อผิดพลาดอย่างที่ Quartermeister แสดงให้เห็น ไดนามิกอาจใช้ตัวดำเนินการแปลก ๆtrueซึ่งอาจทำให้yไม่มีการเตรียมใช้งาน

นี่เป็นคำแนะนำขั้นต่ำ:

class Program
{
    static bool M(out int x) 
    { 
        x = 123; 
        return true; 
    }
    static int N(dynamic d)
    {
        int y;
        if(d || M(out y))
            y = 10;
        return y; 
    }
}

ฉันไม่เห็นเหตุผลว่าทำไมจึงควรผิดกฎหมาย ถ้าคุณแทนที่ไดนามิกด้วยบูลมันจะคอมไพล์ได้ดี

พรุ่งนี้ฉันจะพบกับทีม C # ฉันจะพูดถึงพวกเขา ขออภัยในความผิดพลาด!


6
ฉันดีใจที่รู้ว่าฉันจะไม่บ้า :) ตั้งแต่นั้นมาฉันได้อัปเดตโค้ดของฉันให้พึ่งพา TryParse เท่านั้นดังนั้นฉันจึงตั้งค่าสำหรับ ขอบคุณสำหรับข้อมูลเชิงลึก!
Brandon Martinez

4
@NominSim: สมมติว่าการวิเคราะห์รันไทม์ล้มเหลว: ข้อยกเว้นจะถูกทิ้งก่อนที่จะอ่านโลคัล สมมติว่าการวิเคราะห์รันไทม์สำเร็จ: จากนั้นที่รันไทม์ d เป็นจริงและตั้งค่า y หรือ d เป็นเท็จและ M ตั้งค่า y ไม่ว่าจะด้วยวิธีใดก็ตั้งค่า y ความจริงที่ว่าการวิเคราะห์ถูกเลื่อนออกไปจนกว่ารันไทม์จะไม่เปลี่ยนแปลงอะไรเลย
Eric Lippert

2
ในกรณีที่มีใครสงสัย: ฉันเพิ่งตรวจสอบและคอมไพเลอร์ Mono ทำให้ถูกต้อง imgur.com/g47oquT
ด่านเต่า

17
ฉันคิดว่าพฤติกรรมของคอมไพเลอร์นั้นถูกต้องจริง ๆ เนื่องจากค่าของdอาจเป็นประเภทที่มีตัวtrueดำเนินการมากเกินไป ฉันได้โพสต์คำตอบพร้อมตัวอย่างว่าไม่มีการใช้สาขาใด
Quartermeister

2
@Quartermeister ซึ่งในกรณีนี้คอมไพเลอร์ Mono จะเข้าใจผิด :)
porges

52

เป็นไปได้ที่ตัวแปรจะไม่ถูกกำหนดหากค่าของนิพจน์ไดนามิกเป็นประเภทที่มีตัวดำเนินการมากเกินไปtrueผู้ประกอบการ

ตัว||ดำเนินการจะเรียกให้trueโอเปอเรเตอร์ตัดสินใจว่าจะประเมินด้านขวามือหรือไม่จากนั้นifคำสั่งจะเรียกให้ตัวtrueดำเนินการตัดสินใจว่าจะประเมินร่างกายหรือไม่ สำหรับคนปกติboolแล้วสิ่งเหล่านี้จะส่งคืนผลลัพธ์เดียวกันเสมอและจะได้รับการประเมินอย่างแน่นอน แต่สำหรับตัวดำเนินการที่ผู้ใช้กำหนดเองไม่มีการรับประกันเช่นนั้น!

จาก repro ของ Eric Lippert ต่อไปนี้เป็นโปรแกรมสั้น ๆ และสมบูรณ์ที่แสดงให้เห็นถึงกรณีที่จะไม่มีการเรียกใช้เส้นทางใดและตัวแปรจะมีค่าเริ่มต้น:

using System;

class Program
{
    static bool M(out int x)
    {
        x = 123;
        return true;
    }

    static int N(dynamic d)
    {
        int y = 3;
        if (d || M(out y))
            y = 10;
        return y;
    }

    static void Main(string[] args)
    {
        var result = N(new EvilBool());
        // Prints 3!
        Console.WriteLine(result);
    }
}

class EvilBool
{
    private bool value;

    public static bool operator true(EvilBool b)
    {
        // Return true the first time this is called
        // and false the second time
        b.value = !b.value;
        return b.value;
    }

    public static bool operator false(EvilBool b)
    {
        throw new NotImplementedException();
    }
}

8
ทำงานได้ดีที่นี่ ฉันได้ส่งสิ่งนี้ไปให้ทีมทดสอบและออกแบบ C # แล้ว ฉันจะดูว่าพวกเขามีความคิดเห็นอย่างไรเมื่อเจอกันพรุ่งนี้
Eric Lippert

3
นี่เป็นเรื่องแปลกมากสำหรับฉัน ทำไมdต้องประเมินสองครั้ง? (ผมไม่ได้โต้แย้งว่ามันเห็นได้ชัดว่าเป็นตามที่คุณได้แสดง.) ฉันคาดว่าจะมีผลการประเมินของtrue(จากการภาวนาประกอบการรายแรกสาเหตุโดย||) ที่จะ "ผ่านไป" เพื่อifคำสั่ง นั่นคือสิ่งที่จะเกิดขึ้นอย่างแน่นอนหากคุณเรียกใช้ฟังก์ชันที่นั่น
ด่านเต๋า

3
@DanTao: การแสดงออกdจะถูกประเมินเพียงครั้งเดียวตามที่คุณคาดหวัง มันเป็นtrueผู้ประกอบการที่ถูกเรียกสองครั้งครั้งโดยและครั้งเดียวโดย|| if
Quartermeister

2
@DanTao: var cond = d || M(out y); if (cond) { ... }มันอาจจะมีความชัดเจนมากขึ้นถ้าเราใส่พวกเขาในงบเฉพาะกิจการ อันดับแรกเราประเมินdเพื่อรับการEvilBoolอ้างอิงวัตถุ ในการประเมิน||เราจะเรียกใช้EvilBool.trueข้อมูลอ้างอิงนั้นก่อน ว่าผลตอบแทนที่แท้จริงดังนั้นเราจึงลัดวงจรและไม่เรียกและจากนั้นกำหนดอ้างอิงถึงM condจากนั้นเราไปที่ifคำสั่ง คำสั่งประเมินสภาพของตนโดยการโทรif EvilBool.true
Quartermeister

2
ตอนนี้มันเจ๋งมาก ฉันไม่รู้ว่ามีตัวดำเนินการจริงหรือเท็จ
IllidanS4 รองรับ Monica

7

จาก MSDN (เน้นของฉัน):

ชนิดแบบไดนามิกจะช่วยให้การดำเนินงานในการที่จะเกิดขึ้นกับบายพาสเวลารวบรวมตรวจสอบประเภท แต่การดำเนินการเหล่านี้จะได้รับการแก้ไขในขณะทำงานได้รับการแก้ไขในเวลาทำงานประเภทไดนามิกช่วยลดความยุ่งยากในการเข้าถึง COM API เช่น Office Automation API และ API แบบไดนามิกเช่นไลบรารี IronPython และ HTML Document Object Model (DOM)

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

เนื่องจากคอมไพลเลอร์ไม่พิมพ์ check หรือแก้ไขการดำเนินการใด ๆ ที่มีนิพจน์ประเภทไดนามิกจึงไม่สามารถรับรองได้ว่าตัวแปรจะถูกกำหนดผ่านการใช้TryParse().


หากตรงตามเงื่อนไขแรกจะnumberGroupsถูกกำหนด (ในif trueบล็อก) หากไม่เป็นเช่นนั้นเงื่อนไขที่สองจะรับประกันการมอบหมาย (ผ่านout)
leppie

1
นั่นเป็นความคิดที่น่าสนใจแต่โค้ดจะรวบรวมได้ดีโดยไม่ต้องใช้myString == null(อาศัยเฉพาะTryParse)
Brandon Martinez

1
@leppie ประเด็นก็คือเนื่องจากเงื่อนไขแรก (ดังนั้นifนิพจน์ทั้งหมด) เกี่ยวข้องกับdynamicตัวแปรจึงไม่ได้รับการแก้ไขในเวลาคอมไพล์ (คอมไพเลอร์จึงไม่สามารถตั้งสมมติฐานเหล่านั้นได้)
NominSim

@NominSim: ฉันเห็นจุดของคุณ :) +1 อาจเป็นการเสียสละจากคอมไพเลอร์ (ผิดกฎ C #) แต่คำแนะนำอื่น ๆ ดูเหมือนจะบ่งบอกถึงข้อบกพร่อง ตัวอย่างข้อมูลของ Eric แสดงให้เห็นว่านี่ไม่ใช่การเสียสละ แต่เป็นข้อบกพร่อง
leppie

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