Roslyn รวบรวมโค้ดไม่สำเร็จ


95

หลังจากที่ฉันย้ายโปรเจ็กต์ของฉันจาก VS2013 เป็น VS2015 โปรเจ็กต์จะไม่สร้างอีกต่อไป ข้อผิดพลาดในการคอมไพล์เกิดขึ้นในคำสั่ง LINQ ต่อไปนี้:

static void Main(string[] args)
{
    decimal a, b;
    IEnumerable<dynamic> array = new string[] { "10", "20", "30" };
    var result = (from v in array
                  where decimal.TryParse(v, out a) && decimal.TryParse("15", out b) && a <= b // Error here
                  orderby decimal.Parse(v)
                  select v).ToArray();
}

คอมไพเลอร์ส่งคืนข้อผิดพลาด:

ข้อผิดพลาด CS0165 การใช้ตัวแปรภายในที่ไม่ได้กำหนด 'b'

สาเหตุของปัญหานี้คืออะไร? เป็นไปได้ไหมที่จะแก้ไขผ่านการตั้งค่าคอมไพเลอร์


11
@BinaryWorrier: ทำไม? จะใช้bหลังจากกำหนดผ่านoutพารามิเตอร์เท่านั้น
Jon Skeet

1
เอกสาร VS 2015 ระบุว่า "แม้ว่าตัวแปรที่ส่งผ่านเนื่องจากอาร์กิวเมนต์ไม่จำเป็นต้องถูกกำหนดค่าเริ่มต้นก่อนที่จะส่งผ่าน แต่เมธอดที่เรียกนั้นจำเป็นต้องกำหนดค่าก่อนที่เมธอดจะส่งคืน" ดังนั้นสิ่งนี้จึงดูเหมือนข้อผิดพลาดใช่รับประกันได้ว่าจะเริ่มต้นโดย tryParse นั้น
Rup

3
โดยไม่คำนึงถึงข้อผิดพลาดรหัสนี้เป็นตัวอย่างของทุกสิ่งที่ไม่ดีเกี่ยวกับoutอาร์กิวเมนต์ สิ่งนั้นจะTryParseส่งคืนค่าที่เป็นโมฆะ (หรือเทียบเท่า)
Konrad Rudolph

1
@KonradRudolph where (a = decimal.TryParse(v)).HasValue && (b = decimal.TryParse(v)).HasValue && a <= bดูดีขึ้นมาก
Rawling

2
โปรดทราบว่าคุณสามารถทำให้สิ่งนี้ง่ายขึ้นdecimal a, b; var q = decimal.TryParse((dynamic)"10", out a) && decimal.TryParse("15", out b) && a <= b;ได้ ฉันได้เปิดจุดบกพร่องของ Roslynเพื่อเพิ่มสิ่งนี้
Rawling

คำตอบ:


112

อะไรทำให้เกิดปัญหานี้

ดูเหมือนว่าคอมไพเลอร์บั๊กสำหรับฉัน อย่างน้อยก็ทำได้ แม้ว่านิพจน์decimal.TryParse(v, out a)และdecimal.TryParse(v, out b)จะได้รับการประเมินแบบไดนามิก แต่ฉันคาดว่าคอมไพเลอร์จะยังคงเข้าใจว่าเมื่อถึงเวลาที่กำหนดa <= bทั้งสองอย่างaและbได้รับมอบหมายอย่างแน่นอน แม้จะมีความแปลกประหลาดที่คุณสามารถทำได้ในการพิมพ์แบบไดนามิกฉันคาดหวังว่าจะประเมินa <= bหลังจากประเมินการTryParseโทรทั้งสองครั้งแล้วเท่านั้น

อย่างไรก็ตามปรากฎว่าด้วยตัวดำเนินการและการแปลงที่ยุ่งยากมันเป็นไปได้ทั้งหมดที่จะมีนิพจน์A && B && Cที่ประเมินค่าAและCแต่ไม่ใช่B- หากคุณมีไหวพริบเพียงพอ ดูรายงานข้อบกพร่องของ Roslynสำหรับตัวอย่างอันชาญฉลาดของ Neal Gafter

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

เป็นไปได้ไหมที่จะแก้ไขผ่านการตั้งค่าคอมไพเลอร์

ไม่ แต่มีทางเลือกอื่นที่หลีกเลี่ยงข้อผิดพลาด

ประการแรกคุณสามารถหยุดไม่ให้เป็นแบบไดนามิก - หากคุณรู้ว่าคุณจะใช้สตริงเพียงอย่างเดียวคุณสามารถใช้IEnumerable<string> หรือกำหนดตัวแปรช่วงvเป็นประเภทstring(เช่นfrom string v in array) ได้ นั่นจะเป็นตัวเลือกที่ฉันต้องการ

หากคุณต้องการให้ไดนามิกจริงๆเพียงแค่ให้bค่าเริ่มต้นด้วย:

decimal a, b = 0m;

สิ่งนี้จะไม่ทำอันตรายใด ๆ - เรารู้ดีว่าจริงๆแล้วการประเมินแบบไดนามิกของคุณจะไม่ทำอะไรที่บ้าคลั่งดังนั้นคุณจะยังคงกำหนดค่าให้bก่อนที่คุณจะใช้มันทำให้ค่าเริ่มต้นไม่เกี่ยวข้อง

นอกจากนี้ดูเหมือนว่าการเพิ่มวงเล็บก็ใช้ได้เช่นกัน:

where decimal.TryParse(v, out a) && (decimal.TryParse("15", out b) && a <= b)

นั่นจะเปลี่ยนจุดที่ทริกเกอร์ความละเอียดโอเวอร์โหลดชิ้นต่างๆและเกิดขึ้นเพื่อทำให้คอมไพเลอร์พอใจ

นอกจากนี้หนึ่งในปัญหาที่ยังเหลือ - กฎข้อมูลจำเพาะของที่ได้รับมอบหมายที่ชัดเจนกับ&&ความต้องการผู้ประกอบการที่จะชี้แจงให้กับรัฐที่พวกเขาใช้เฉพาะเมื่อ&&ผู้ประกอบการจะถูกนำมาใช้ในการดำเนินงานของ "ปกติ" มีสองboolตัวถูกดำเนินการ ฉันจะพยายามทำให้แน่ใจว่าสิ่งนี้ได้รับการแก้ไขสำหรับมาตรฐาน ECMA ถัดไป


ใช่ การใช้IEnumerable<string>หรือเพิ่มวงเล็บใช้ได้ผลสำหรับฉัน ตอนนี้คอมไพเลอร์สร้างขึ้นโดยไม่มีข้อผิดพลาด
ramil89

1
การใช้decimal a, b = 0m;อาจลบข้อผิดพลาด แต่a <= bจะใช้เสมอ0mเนื่องจากยังไม่ได้คำนวณค่าออก
Paw Baltzersen

12
@PawBaltzersen: อะไรทำให้คุณคิดอย่างนั้น? มันจะถูกกำหนดก่อนการเปรียบเทียบเสมอ - เป็นเพียงการที่คอมไพเลอร์ไม่สามารถพิสูจน์ได้ด้วยเหตุผลบางประการ (โดยทั่วไปข้อผิดพลาด)
Jon Skeet

1
มีวิธีการแยกวิเคราะห์โดยไม่มีผลข้างเคียงเช่น. decimal? TryParseDecimal(string txt)อาจเป็นวิธีแก้ปัญหาด้วย
zahir

1
ฉันสงสัยว่ามันขี้เกียจเริ่มต้น; มันคิดว่า "ถ้าอันแรกเป็นจริงฉันก็ไม่จำเป็นต้องประเมินอันที่สองซึ่งหมายความว่าbอาจไม่ได้รับมอบหมาย"; ฉันรู้ว่านั่นเป็นเหตุผลที่ไม่ถูกต้อง แต่มันอธิบายได้ว่าทำไมวงเล็บจึงแก้ไขมัน ...
durron597

21

สิ่งนี้ดูเหมือนจะเป็นจุดบกพร่องหรืออย่างน้อยที่สุดก็คือการถดถอยในคอมไพเลอร์ Roslyn มีการยื่นข้อบกพร่องต่อไปนี้เพื่อติดตาม:

https://github.com/dotnet/roslyn/issues/4509

ในขณะเดียวกันคำตอบที่ยอดเยี่ยมของจอนมีอยู่สองประการ


นอกจากนี้ข้อผิดพลาดนี้ซึ่งตั้งข้อสังเกตว่าตอนนี้ไม่เกี่ยวข้องกับ LINQ ...
Rawling

16

เนื่องจากฉันได้รับการศึกษาอย่างหนักในรายงานข้อบกพร่องฉันจะพยายามอธิบายเรื่องนี้ด้วยตัวเอง


ลองนึกภาพTบางประเภทที่ผู้ใช้กำหนดด้วยการหล่อโดยปริยายที่จะboolสลับกันไปมาระหว่างที่falseและเริ่มต้นด้วยtrue falseเท่าที่คอมไพลเลอร์รู้dynamicอาร์กิวเมนต์แรกถึงตัวแรก&&อาจประเมินเป็นประเภทนั้นดังนั้นจึงต้องมองโลกในแง่ร้าย

จากนั้นหากปล่อยให้โค้ดคอมไพล์สิ่งนี้อาจเกิดขึ้น:

  • เมื่อตัวประสานแบบไดนามิกประเมินค่าแรก&&จะทำสิ่งต่อไปนี้:
    • ประเมินอาร์กิวเมนต์แรก
    • มันเป็นT- boolโดยปริยายโยนมันทิ้งไป
    • โอ้มันเป็นfalseเช่นนั้นเราไม่จำเป็นต้องประเมินข้อโต้แย้งที่สอง
    • ทำให้ผลลัพธ์ของการ&&ประเมินเป็นอาร์กิวเมนต์แรก (ไม่ไม่falseด้วยเหตุผลบางประการ)
  • เมื่อตัวประสานแบบไดนามิกประเมินวินาที&&จะทำสิ่งต่อไปนี้:
    • ประเมินอาร์กิวเมนต์แรก
    • มันเป็นT- boolโดยปริยายโยนมันทิ้งไป
    • โอ้มันเป็นtrueเช่นนั้นเพื่อประเมินข้อโต้แย้งที่สอง
    • ... โอ้อึbไม่ได้รับมอบหมาย

กล่าวโดยสรุปสั้น ๆ ว่ามีกฎ "การมอบหมายที่แน่นอน" พิเศษซึ่งช่วยให้เราสามารถบอกได้ว่าตัวแปรนั้น "กำหนดแน่นอน" หรือ "ไม่ได้กำหนดแน่นอน" แต่ยังรวมถึง "กำหนดหลังfalseคำสั่ง" หรือ "แน่นอน มอบหมายหลังtrueคำสั่ง ".

ที่มีอยู่เหล่านี้เพื่อที่ว่าเมื่อจัดการกับ&&และ||(และ!และ??และ?:) คอมไพเลอร์สามารถตรวจสอบว่าตัวแปรที่อาจได้รับมอบหมายในสาขาเฉพาะของนิพจน์บูลีนที่ซับซ้อน

แต่เหล่านี้ทำงานเท่านั้นในขณะที่ประเภทการแสดงออกยังคงบูล เมื่อส่วนหนึ่งของนิพจน์เป็นdynamic(หรือประเภทคงที่ที่ไม่ใช่บูลีน) เราไม่สามารถพูดได้อย่างน่าเชื่อถืออีกต่อไปว่านิพจน์นั้นเป็นtrueหรือfalse- ในครั้งต่อไปที่เราโยนมันboolเพื่อตัดสินใจว่าจะใช้สาขาใดมันอาจเปลี่ยนใจ


อัปเดต: ขณะนี้ได้รับการแก้ไขและจัดทำเป็นเอกสาร :

กฎการกำหนดที่แน่นอนที่ใช้โดยคอมไพเลอร์ก่อนหน้าสำหรับนิพจน์แบบไดนามิกอนุญาตให้มีโค้ดบางกรณีที่อาจส่งผลให้มีการอ่านตัวแปรที่ไม่ได้กำหนดไว้อย่างแน่นอน ดูhttps://github.com/dotnet/roslyn/issues/4509สำหรับรายงานเรื่องนี้

...

เนื่องจากความเป็นไปได้นี้คอมไพเลอร์จะต้องไม่อนุญาตให้คอมไพล์โปรแกรมนี้หาก val ไม่มีค่าเริ่มต้น คอมไพเลอร์เวอร์ชันก่อนหน้า (ก่อน VS2015) อนุญาตให้โปรแกรมนี้คอมไพล์แม้ว่า val จะไม่มีค่าเริ่มต้นก็ตาม ขณะนี้ Roslyn วินิจฉัยความพยายามนี้เพื่ออ่านตัวแปรที่อาจไม่ได้กำหนดค่าเริ่มต้น


1
เมื่อใช้ VS2013 บนเครื่องอื่นของฉันฉันได้จัดการอ่านหน่วยความจำที่ไม่ได้กำหนดโดยใช้สิ่งนี้แล้ว มันไม่น่าตื่นเต้นเท่าไหร่ :(
Rawling

คุณสามารถอ่านตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นด้วยผู้รับมอบสิทธิ์อย่างง่าย สร้างผู้รับมอบสิทธิ์ที่ได้รับไปใช้วิธีการที่มีout refมันจะทำอย่างมีความสุขและจะทำให้ตัวแปรที่กำหนดโดยไม่ต้องเปลี่ยนค่า
IllidanS4 รองรับ Monica

ด้วยความอยากรู้ฉันจึงทดสอบตัวอย่างข้อมูลนั้นด้วย C # v4 ด้วยความอยากรู้อยากเห็น - คอมไพเลอร์ตัดสินใจใช้ตัวดำเนินการอย่างไรfalse/ trueซึ่งตรงข้ามกับตัวดำเนินการ cast โดยนัย? ในประเทศก็จะเรียกimplicit operator boolในอาร์กิวเมนต์แรกแล้วเรียกตัวถูกดำเนินการที่สองสายoperator falseในตัวถูกดำเนินการก่อนตามด้วยimplicit operator boolในตัวถูกดำเนินการครั้งแรกอีกครั้ง สิ่งนี้ไม่สมเหตุสมผลสำหรับฉันตัวถูกดำเนินการตัวแรกควรต้มเป็นบูลีนครั้งเดียวไม่ใช่เหรอ?
Rob

@ Rob นี่เป็นกรณีที่ถูกdynamicล่ามโซ่&&หรือไม่? ฉันเคยเห็นว่าโดยทั่วไปแล้วไป (1) ประเมินอาร์กิวเมนต์แรก (2) ใช้การร่ายโดยนัยเพื่อดูว่าฉันสามารถลัดวงจรได้หรือไม่ (3) ฉันทำไม่ได้ดังนั้นจึงทำให้อาร์กิวเมนต์ที่สอง (4) ตอนนี้ฉันรู้ทั้งสองประเภทแล้วฉัน สามารถเห็นสิ่งที่ดีที่สุด&&คือ&ตัวดำเนินการเรียกที่ผู้ใช้กำหนด(5) falseในอาร์กิวเมนต์แรกเพื่อดูว่าฉันสามารถลัดวงจรได้หรือไม่ (6) ฉันทำได้ (เพราะfalseและimplicit boolไม่เห็นด้วย) ดังนั้นผลลัพธ์คืออาร์กิวเมนต์แรก ... แล้วถัดไป&&, (7) ใช้การร่ายโดยนัยเพื่อดูว่าฉันสามารถลัดวงจรได้หรือไม่ (อีกครั้ง)
Rawling

@ IllidanS4 ฟังดูน่าสนใจ แต่ยังไม่พบวิธีทำ ขอตัวอย่างหน่อยได้ไหม
Rawling

15

นี่ไม่ใช่บั๊ก ดูhttps://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713สำหรับตัวอย่างว่านิพจน์ไดนามิกของแบบฟอร์มนี้สามารถปล่อยให้ตัวแปร out นั้นไม่ได้กำหนดได้อย่างไร


1
เนื่องจากคำตอบของฉันได้รับการยอมรับและได้รับการโหวตสูงฉันจึงได้แก้ไขเพื่อระบุความละเอียด ขอบคุณสำหรับการทำงานทั้งหมดของคุณ - รวมถึงการอธิบายข้อผิดพลาดของฉันให้ฉันด้วย :)
Jon Skeet
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.