ทำตัวดำเนินการลัดวงจร || และ && มีอยู่สำหรับบูลีนที่เป็นโมฆะ? บางครั้ง RuntimeBinder ก็คิดเช่นนั้น


84

ฉันอ่านข้อกำหนดภาษา C # บนตัวดำเนินการตรรกะแบบมีเงื่อนไข ||และ&&หรือที่เรียกว่าตัวดำเนินการทางตรรกะที่ลัดวงจร สำหรับฉันแล้วมันดูเหมือนไม่ชัดเจนว่าสิ่งเหล่านี้มีอยู่สำหรับบูลีนที่เป็นโมฆะหรือไม่เช่นประเภทตัวถูกดำเนินการNullable<bool>(เขียนด้วยbool?) ดังนั้นฉันจึงลองด้วยการพิมพ์แบบไม่ไดนามิก:

bool a = true;
bool? b = null;
bool? xxxx = b || a;  // compile-time error, || can't be applied to these types

ดูเหมือนจะช่วยชำระคำถามได้ (ฉันไม่เข้าใจข้อกำหนดอย่างชัดเจน แต่สมมติว่าการใช้งานคอมไพเลอร์ Visual C # ถูกต้องตอนนี้ฉันรู้แล้ว)

อย่างไรก็ตามฉันก็อยากลองdynamicผูกด้วยเช่นกัน ดังนั้นฉันจึงลองสิ่งนี้แทน:

static class Program
{
  static dynamic A
  {
    get
    {
      Console.WriteLine("'A' evaluated");
      return true;
    }
  }
  static dynamic B
  {
    get
    {
      Console.WriteLine("'B' evaluated");
      return null;
    }
  }

  static void Main()
  {
    dynamic x = A | B;
    Console.WriteLine((object)x);
    dynamic y = A & B;
    Console.WriteLine((object)y);

    dynamic xx = A || B;
    Console.WriteLine((object)xx);
    dynamic yy = A && B;
    Console.WriteLine((object)yy);
  }
}

ผลลัพธ์ที่น่าประหลาดใจคือการดำเนินการนี้โดยไม่มีข้อยกเว้น

ดีxและyจะไม่น่าแปลกใจที่การประกาศของพวกเขานำไปสู่คุณสมบัติทั้งสองถูกดึงและค่านิยมที่เกิดขึ้นเป็นไปตามคาดxเป็นtrueและเป็นynull

แต่การประเมินผลสำหรับxxของA || Bนำไปสู่การไม่มีข้อยกเว้นเวลาที่มีผลผูกพันและมีเพียงทรัพย์สินที่ได้อ่านไม่ได้A Bทำไมสิ่งนี้ถึงเกิดขึ้น? อย่างที่คุณบอกเราสามารถเปลี่ยนผู้เริ่มต้นBให้ส่งคืนวัตถุที่บ้าคลั่งเช่น"Hello world"และxxจะยังคงประเมินtrueโดยไม่มีปัญหาการผูกมัด ...

การประเมินA && B(สำหรับyy) ยังทำให้ไม่มีข้อผิดพลาดเวลาผูกมัด และที่นี่คุณสมบัติทั้งสองจะถูกดึงออกมาแน่นอน เหตุใดสิ่งนี้จึงได้รับอนุญาตจากตัวประสานรันไทม์ ถ้าวัตถุที่ส่งคืนจากBเปลี่ยนเป็นวัตถุ "ไม่ดี" (เช่น a string) จะมีข้อยกเว้นการผูก

พฤติกรรมนี้ถูกต้องหรือไม่? (คุณจะสรุปได้อย่างไรว่าจากข้อมูลจำเพาะ?)

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

(พยายามกับคอมไพเลอร์ C # ของ Visual Studio 2013 และเวอร์ชันรันไทม์. NET 4.5.2)


4
ไม่มีอินสแตนซ์ที่Nullable<Boolean>เกี่ยวข้องเลยมีเพียงบูลีนชนิดบรรจุกล่องเท่านั้นที่ถือว่าเป็นdynamic- การทดสอบของคุณbool?ไม่เกี่ยวข้อง (แน่นอนว่านี่ไม่ใช่คำตอบที่สมบูรณ์มีเพียงเชื้อเดียวเท่านั้น)
Jeroen Mostert

3
A || Bทำให้จำนวนหนึ่งของความรู้สึกในการที่คุณไม่ต้องการที่จะประเมินผลBเว้นแต่Aเป็นเท็จซึ่งก็ไม่ได้ ดังนั้นคุณจะไม่ทราบประเภทของนิพจน์จริงๆ A && Bรุ่นที่น่าแปลกใจมากขึ้น - ฉันจะเห็นสิ่งที่ฉันจะพบในสเปค
Jon Skeet

2
@JeroenMostert: เว้นแต่คอมไพเลอร์จะตัดสินใจว่าถ้าประเภทของAคือboolและค่าของBคือnullตัวbool && bool?ดำเนินการอาจเกี่ยวข้อง
Jon Skeet

4
ที่น่าสนใจคือดูเหมือนว่าสิ่งนี้ได้เปิดเผยข้อบกพร่องของคอมไพเลอร์หรือข้อมูลจำเพาะ ข้อกำหนด C # 5.0 สำหรับการ&&พูดถึงการแก้ไขมันเหมือนกับว่ามันเป็น&แทนและโดยเฉพาะรวมถึงกรณีที่ตัวถูกดำเนินการทั้งสองอยู่bool?- แต่ส่วนถัดไปที่อ้างถึงไม่ได้จัดการกับกรณีที่เป็นโมฆะ ฉันสามารถเพิ่มคำตอบที่มีรายละเอียดเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ แต่มันไม่สามารถอธิบายได้ทั้งหมด
Jon Skeet

14
ฉันได้ส่งอีเมลถึง Mads เกี่ยวกับปัญหา spec เพื่อดูว่าเป็นเพียงปัญหาในการอ่าน ...
Jon Skeet

คำตอบ:


67

ก่อนอื่นขอขอบคุณที่ชี้ให้เห็นว่าข้อมูลจำเพาะไม่ชัดเจนในกรณีที่ไม่ได้ไดนามิก nullable-bool ฉันจะแก้ไขในเวอร์ชันอนาคต พฤติกรรมของคอมไพเลอร์เป็นพฤติกรรมที่ตั้งใจไว้ &&และ||ไม่ควรทำงานกับบูลที่เป็นโมฆะ

แม้ว่าตัวประสานแบบไดนามิกจะไม่ใช้ข้อ จำกัด นี้ แต่จะผูกการดำเนินการคอมโพเนนต์แยกกัน: the &/ |และ?:. ดังนั้นจึงสามารถทำให้สับสนได้หากตัวถูกดำเนินการตัวแรกเป็นtrueหรือfalse(ซึ่งเป็นค่าบูลีนและได้รับอนุญาตให้เป็นตัวถูกดำเนินการตัวแรก?:) แต่ถ้าคุณให้nullเป็นตัวถูกดำเนินการตัวแรก (เช่นถ้าคุณลองB && Aในตัวอย่างด้านบน) คุณจะทำ รับข้อยกเว้นการรวมรันไทม์

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

  • ประเมินตัวถูกดำเนินการด้านซ้าย (เรียกว่าผลลัพธ์x)
  • พยายามเปลี่ยนboolเป็นการแปลงโดยนัยหรือtrueหรือfalseตัวดำเนินการ (ล้มเหลวหากไม่สามารถ)
  • ใช้xเป็นเงื่อนไขในการ?:ดำเนินการ
  • ในสาขาที่แท้จริงให้ใช้xเป็นผลลัพธ์
  • ในสาขาเท็จตอนนี้ประเมินตัวถูกดำเนินการที่สอง (เรียกว่าผลลัพธ์y)
  • พยายามผูก&หรือ|ตัวดำเนินการตามประเภทรันไทม์ของxและy(ล้มเหลวหากไม่สามารถ)
  • ใช้ตัวดำเนินการที่เลือก

นี่คือลักษณะการทำงานที่ปล่อยให้ผ่านชุดตัวถูกดำเนินการที่ "ผิดกฎหมาย" บางตัว: ตัวดำเนิน?:การถือว่าตัวถูกดำเนินการแรกเป็นบูลีนที่ไม่เป็นโมฆะได้สำเร็จ&หรือ|ตัวดำเนินการถือว่าเป็นบูลีนที่เป็นโมฆะได้สำเร็จและทั้งสองไม่เคยประสานงานกันเพื่อตรวจสอบว่าพวกเขาเห็นด้วย .

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

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

บ้า


ฉันเห็นได้ว่าตัวดำเนินการลัดวงจรเหล่านี้มีความพิเศษเนื่องจากการผูกแบบไดนามิกเราไม่ได้รับอนุญาตให้ทราบประเภทของตัวถูกดำเนินการที่สองในกรณีที่เราลัดวงจร บางทีสเป็คควรพูดถึง? แน่นอนเนื่องจากทุกอย่างที่อยู่ภายในdynamicจะบรรจุกล่องเราไม่สามารถบอกความแตกต่างระหว่างbool?ที่HasValueและ bool"ง่าย"
Jeppe Stig Nielsen

6

พฤติกรรมนี้ถูกต้องหรือไม่?

ใช่ฉันค่อนข้างมั่นใจว่าเป็นอย่างนั้น

คุณสามารถสรุปได้อย่างไรว่าจากข้อมูลจำเพาะ?

ส่วน 7.12 ของข้อกำหนด C # เวอร์ชัน 5.0 มีข้อมูลเกี่ยวกับตัวดำเนินการตามเงื่อนไข&&และ||ความสัมพันธ์แบบไดนามิกเกี่ยวข้องกับพวกเขาอย่างไร ส่วนที่เกี่ยวข้อง:

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

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

  • การดำเนินการ x && y ถูกประเมินเป็น T. false (x)? x: T. & (x, y) โดยที่ T. false (x) คือการเรียกใช้ตัวดำเนินการที่ประกาศเท็จใน T และ T & (x, y) เป็นการเรียกใช้ตัวดำเนินการที่เลือก &
  • การดำเนินการ x || y ถูกประเมินเป็น T.true (x)? x: T. | (x, y) โดยที่ T.true (x) เป็นการเรียกใช้ตัวดำเนินการจริงที่ประกาศใน T และ T. | (x, y) คือการเรียกใช้ตัวดำเนินการที่เลือก |.

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

แต่การประเมินค่า xx ของ A || B นำไปสู่การไม่มีข้อยกเว้นเวลาผูกพันและมีเพียงคุณสมบัติ A เท่านั้นที่ถูกอ่านไม่ใช่ B ทำไมจึงเกิดขึ้น

สำหรับผู้ประกอบการที่เรารู้ว่าต่อไปนี้|| true(A) ? A : |(A, B)เราลัดวงจรดังนั้นเราจะไม่ได้รับข้อยกเว้นเวลาผูกพัน แม้ว่าจะAเป็นfalseแต่เราก็ยังไม่ได้รับข้อยกเว้นการรวมรันไทม์เนื่องจากขั้นตอนการแก้ปัญหาที่ระบุ ถ้าAเป็นfalseเช่นนั้นเราจะ|ดำเนินการตัวดำเนินการซึ่งสามารถจัดการกับค่าว่างได้สำเร็จตามหัวข้อ 7.11.4

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

ด้วยเหตุผลที่คล้ายกันอันนี้ก็ใช้ได้เช่นกัน มีการประเมินว่า&& สามารถแปลงเป็น a ได้สำเร็จดังนั้นจึงไม่มีปัญหา เนื่องจากเป็นโมฆะตัวดำเนินการจึงถูกยก (ส่วน 7.3.7) จากตัวดำเนินการที่รับค่าพารามิเตอร์ดังนั้นจึงไม่มีข้อยกเว้นรันไทม์false(x) ? x : &(x, y)AboolB&boolbool?

สำหรับตัวดำเนินการที่มีเงื่อนไขทั้งสองตัวถ้าBเป็นสิ่งอื่นที่ไม่ใช่บูล (หรือไดนามิกแบบว่าง) การรวมรันไทม์จะล้มเหลวเนื่องจากไม่พบโอเวอร์โหลดที่ใช้บูลและไม่ใช่บูลเป็นพารามิเตอร์ อย่างไรก็ตามสิ่งนี้จะเกิดขึ้นหากAไม่สามารถปฏิบัติตามเงื่อนไขแรกสำหรับตัวดำเนินการ ( truefor ||, falsefor &&) สาเหตุที่เกิดขึ้นเนื่องจากการเชื่อมโยงแบบไดนามิกค่อนข้างขี้เกียจ มันจะไม่พยายามผูกตัวดำเนินการทางตรรกะเว้นแต่Aจะเป็นเท็จและต้องไปตามเส้นทางนั้นเพื่อประเมินตัวดำเนินการทางตรรกะ เมื่อAไม่สามารถปฏิบัติตามเงื่อนไขแรกของตัวดำเนินการได้ก็จะล้มเหลวด้วยข้อยกเว้นการผูก

ถ้าคุณลอง B เป็นตัวถูกดำเนินการตัวแรกทั้ง B || A และ B && A ให้ข้อยกเว้นของ binder รันไทม์

หวังว่าตอนนี้คุณคงรู้แล้วว่าทำไมสิ่งนี้ถึงเกิดขึ้น (หรือฉันอธิบายไม่ถูก) ขั้นตอนแรกในการแก้ไขตัวดำเนินการตามเงื่อนไขนี้คือการใช้ตัวถูกดำเนินการตัวแรกBและใช้หนึ่งในตัวดำเนินการการแปลงบูล ( false(B)หรือtrue(B)) ก่อนที่จะจัดการการดำเนินการทางตรรกะ แน่นอนB, ความเป็นอยู่nullไม่สามารถแปลงเป็นอย่างใดอย่างหนึ่งtrueหรือfalseและอื่น ๆ ยกเว้นรันไทม์ผูกพันที่เกิดขึ้น


ไม่แปลกใจเลยที่dynamicการเชื่อมโยงจะเกิดขึ้นในขณะรันไทม์โดยใช้ประเภทจริงของอินสแตนซ์ไม่ใช่ประเภทเวลาคอมไพล์ (ใบเสนอราคาแรกของคุณ) อ้างที่สองของคุณจะไม่เกี่ยวข้องตั้งแต่ประเภทที่นี่ไม่มี overloads และoperator true กลับเป็นสิ่งอื่นมากกว่าและ มันเป็นเรื่องยากที่จะอ่านข้อมูลจำเพาะในทางที่ช่วยให้การใด ๆ(ในตัวอย่างของฉัน) โดยไม่ได้ช่วยให้ที่และมีการพิมพ์แบบคงที่ booleans nullable คือและมีผลผูกพันที่รวบรวมเวลา แต่ที่ไม่ได้รับอนุญาต operator falseexplicit operatorbooloperator truefalseA && Ba && babbool? abool? b
Jeppe Stig Nielsen

-1

ประเภท Nullable ไม่ได้กำหนดตัวดำเนินการตรรกะแบบมีเงื่อนไข || และ &&. ฉันขอแนะนำให้คุณทำตามรหัส:

bool a = true;
bool? b = null;

bool? xxxxOR = (b.HasValue == true) ? (b.Value || a) : a;
bool? xxxxAND = (b.HasValue == true) ? (b.Value && a) : false;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.