เนื่องจากtrue
ไม่ใช่ประเภทnull + true
สตริงสตริงเป็นอย่างไร
string s = true; //Cannot implicitly convert type 'bool' to 'string'
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'
เหตุผลเบื้องหลังนี้คืออะไร?
เนื่องจากtrue
ไม่ใช่ประเภทnull + true
สตริงสตริงเป็นอย่างไร
string s = true; //Cannot implicitly convert type 'bool' to 'string'
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'
เหตุผลเบื้องหลังนี้คืออะไร?
คำตอบ:
สิ่งนี้อาจดูแปลกประหลาดเพียงทำตามกฎจากข้อกำหนดภาษา C #
จากหัวข้อ 7.3.4:
การดำเนินการของรูปแบบ x op y โดยที่ op เป็นตัวดำเนินการไบนารีที่โอเวอร์โหลดได้ x คือนิพจน์ของประเภท X และ y คือนิพจน์ประเภท Y ได้รับการประมวลผลดังนี้:
- ชุดของตัวดำเนินการที่กำหนดโดยผู้ใช้ที่กำหนดโดย X และ Y สำหรับตัวดำเนินการการดำเนินการ op (x, y) ถูกกำหนด ชุดประกอบด้วยการรวมกันของตัวดำเนินการผู้สมัครที่จัดเตรียมโดย X และตัวดำเนินการที่ได้รับการจัดเตรียมโดย Y ซึ่งแต่ละชุดจะถูกกำหนดโดยใช้กฎของ§7.3.5 ถ้า X และ Y เป็นประเภทเดียวกันหรือถ้า X และ Y มาจากประเภทพื้นฐานทั่วไปตัวดำเนินการที่ใช้ร่วมกันจะเกิดขึ้นในชุดรวมเพียงครั้งเดียว
- หากชุดของตัวดำเนินการที่ผู้ใช้กำหนดเองไม่ว่างเปล่าจะกลายเป็นชุดของตัวดำเนินการที่เป็นตัวเลือกสำหรับการดำเนินการ มิฉะนั้นการใช้งานตัวดำเนินการไบนารีที่กำหนดไว้ล่วงหน้ารวมถึงรูปแบบที่ยกขึ้นจะกลายเป็นชุดของตัวดำเนินการที่เป็นตัวดำเนินการสำหรับการดำเนินการ การใช้งานที่กำหนดไว้ล่วงหน้าของตัวดำเนินการที่กำหนดระบุไว้ในคำอธิบายของตัวดำเนินการ (§7.8ถึง§7.12)
- กฎการแก้ปัญหาโอเวอร์โหลดของ§7.5.3ถูกนำไปใช้กับชุดของตัวดำเนินการที่เป็นตัวเลือกเพื่อเลือกตัวดำเนินการที่ดีที่สุดตามรายการอาร์กิวเมนต์ (x, y) และตัวดำเนินการนี้จะกลายเป็นผลลัพธ์ของกระบวนการแก้ไขปัญหาโอเวอร์โหลด หากความละเอียดเกินพิกัดไม่สามารถเลือกตัวดำเนินการที่ดีที่สุดเพียงตัวเดียวข้อผิดพลาดเวลาเข้าเล่มจะเกิดขึ้น
งั้นเรามาดูกันดีกว่า
X คือประเภท null ที่นี่หรือไม่ใช่ประเภทเลยถ้าคุณต้องการคิดแบบนั้น ไม่มีการจัดหาผู้สมัครใด ๆ Y คือbool
ซึ่งไม่มี+
ตัวดำเนินการที่ผู้ใช้กำหนด ขั้นตอนแรกจึงไม่พบตัวดำเนินการที่ผู้ใช้กำหนด
จากนั้นคอมไพเลอร์จะย้ายไปยังสัญลักษณ์แสดงหัวข้อย่อยที่สองโดยดูผ่านตัวดำเนินการไบนารี + การใช้งานที่กำหนดไว้ล่วงหน้าและรูปแบบที่ยกขึ้น นี่คือรายชื่อในส่วน 7.8.4 ของข้อมูลจำเพาะ
หากคุณมองผ่านผู้ประกอบการที่กำหนดไว้ล่วงหน้าเหล่านั้นเพียงstring operator +(string x, object y)
หนึ่งที่มีผลบังคับใช้ ดังนั้นชุดผู้สมัครจึงมีรายการเดียว นั่นทำให้สัญลักษณ์แสดงหัวข้อย่อยสุดท้ายง่ายมาก ... ความละเอียดเกินจะเลือกตัวดำเนินการนั้นโดยให้ประเภทนิพจน์โดยรวมเป็นstring
.
ประเด็นที่น่าสนใจประการหนึ่งคือสิ่งนี้จะเกิดขึ้นแม้ว่าจะมีตัวดำเนินการที่ผู้ใช้กำหนดเองอื่น ๆ ที่พร้อมใช้งานในประเภทที่ไม่ได้กล่าวถึง ตัวอย่างเช่น:
// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;
ที่ดี Foo
แต่ก็ไม่ได้ใช้สำหรับตัวอักษรโมฆะเพราะคอมไพเลอร์ไม่ทราบว่าจะมองใน รู้เพียงว่าต้องพิจารณาstring
เนื่องจากเป็นตัวดำเนินการที่กำหนดไว้ล่วงหน้าซึ่งระบุไว้อย่างชัดเจนในข้อมูลจำเพาะ (ในความเป็นจริงไม่ใช่ตัวดำเนินการที่กำหนดโดยประเภทสตริง ... 1 ) นั่นหมายความว่าจะไม่สามารถรวบรวม:
// Error: Cannot implicitly convert type 'string' to 'Foo'
Foo f = null + true;
ตัวดำเนินการประเภทที่สองอื่น ๆ จะใช้ตัวดำเนินการอื่นแน่นอน:
var x = null + 0; // x is Nullable<int>
var y = null + 0L; // y is Nullable<long>
var z = null + DayOfWeek.Sunday; // z is Nullable<DayOfWeek>
1คุณอาจสงสัยว่าทำไมไม่มีตัวดำเนินการ string + เป็นคำถามที่สมเหตุสมผลและฉันแค่คาดเดาคำตอบ แต่ลองพิจารณาสำนวนนี้:
string x = a + b + c + d;
หากstring
ไม่มีปลอกพิเศษในคอมไพเลอร์ C # สิ่งนี้จะจบลงอย่างมีประสิทธิภาพ:
string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;
นั่นคือสร้างสตริงกลางสองอันที่ไม่จำเป็น แต่เนื่องจากมีการสนับสนุนพิเศษภายในคอมไพเลอร์ที่เป็นจริงสามารถรวบรวมข้างต้นเป็น:
string x = string.Concat(a, b, c, d);
ซึ่งสามารถสร้างเพียงสตริงเดียวที่มีความยาวพอดีโดยคัดลอกข้อมูลทั้งหมดเพียงครั้งเดียว ดี
true
string
หากนิพจน์ถูกต้องประเภทจะเป็นstring
แต่ในกรณีนี้ความล้มเหลวในการแปลงเป็นสตริงทำให้นิพจน์ทั้งหมดเป็นข้อผิดพลาดและด้วยเหตุนี้จึงไม่มีประเภท
x
string
หมายเหตุว่าลายเซ็นที่ใช้ที่นี่เป็นstring operator+(string, object)
- ก็แปลงbool
ไปobject
(ซึ่งเป็นดี) string
ไม่ได้ที่จะ
เหตุผลก็เพราะว่าเมื่อคุณแนะนำ+
กฎการผูกตัวดำเนินการ C # ก็เข้ามามีบทบาท จะพิจารณาชุดของ+
ตัวดำเนินการที่มีอยู่และเลือกโอเวอร์โหลดที่ดีที่สุด หนึ่งในตัวดำเนินการดังต่อไปนี้
string operator +(string x, object y)
null + true
เกินนี้เข้ากันได้กับประเภทการโต้แย้งในการแสดงออก ดังนั้นมันจะถูกเลือกเป็นผู้ดำเนินการและได้รับการประเมินเป็นหลักซึ่งจะประเมินค่า((string)null) + true
"True"
ส่วน 7.7.4 ของข้อกำหนดภาษา C # มีรายละเอียดเกี่ยวกับความละเอียดนี้
operator+
string
แต่มันมีอยู่ในใจของคอมไพเลอร์เท่านั้นและมันก็แปลเป็นคำเรียกร้องstring.Concat
คอมไพเลอร์ออกตามล่าหาตัวดำเนินการ + () ที่สามารถใช้อาร์กิวเมนต์ว่างก่อน ไม่มีชนิดของค่ามาตรฐานที่มีคุณสมบัติเป็นโมฆะไม่ใช่ค่าที่ถูกต้องสำหรับพวกเขา การจับคู่หนึ่งเดียวคือ System.String.operator + () ไม่มีความคลุมเครือ
อาร์กิวเมนต์ที่ 2 ของตัวดำเนินการนั้นยังเป็นสตริง ที่ไป kapooey ไม่สามารถแปลงบูลเป็นสตริงโดยปริยาย
ที่น่าสนใจคือการใช้ตัวสะท้อนแสงเพื่อตรวจสอบสิ่งที่สร้างขึ้นรหัสต่อไปนี้:
string b = null + true;
Console.WriteLine(b);
ถูกแปลงเป็นสิ่งนี้โดยคอมไพเลอร์:
Console.WriteLine(true);
เหตุผลที่อยู่เบื้องหลัง "การเพิ่มประสิทธิภาพ" นี้ค่อนข้างแปลกที่ฉันต้องบอกและไม่ได้คล้องจองกับการเลือกตัวดำเนินการที่ฉันคาดหวัง
นอกจากนี้รหัสต่อไปนี้:
var b = null + true;
var sb = new StringBuilder(b);
ถูกเปลี่ยนเป็น
string b = true;
StringBuilder sb = new StringBuilder(b);
ที่string b = true;
เป็นจริงไม่ได้รับการยอมรับจากคอมไพเลอร์
null
จะถูกส่งไปยังสตริงว่างและมีตัวแปลงโดยนัยจากบูลเป็นสตริงดังนั้นtrue
จะถูกส่งไปยังสตริงจากนั้น+
จะใช้ตัวดำเนินการ: เหมือนกับ: string str = "" + true ToString ();
ถ้าคุณตรวจสอบด้วย Ildasm:
string str = null + true;
มันดัง:
.locals init ([0] string str)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: box [mscorlib]System.Boolean
IL_0007: call string [mscorlib]System.String::Concat(object)
IL_000c: stloc.0
var b = (null + DateTime.Now); // String
var b = (null + 1); // System.Nullable<Int32> | same with System.Single, System.Double, System.Decimal, System.TimeSpan etc
var b = (null + new Object()); // String | same with any ref type
บ้า?? ไม่ต้องมีเหตุผลเบื้องหลัง
มีคนโทรมาEric Lippert
...
เหตุผลนี้คือความสะดวก (การต่อสตริงเป็นงานทั่วไป)
ดังที่ BoltClock กล่าวไว้ตัวดำเนินการ '+' ถูกกำหนดตามประเภทตัวเลขสตริงและสามารถกำหนดสำหรับประเภทของเราเองได้เช่นกัน (ตัวดำเนินการมากเกินไป)
หากไม่มีตัวดำเนินการ '+' ที่โอเวอร์โหลดในประเภทของอาร์กิวเมนต์และไม่ใช่ประเภทตัวเลขคอมไพเลอร์จะตั้งค่าเริ่มต้นเป็นการต่อสายอักขระ
คอมไพเลอร์แทรกการเรียกString.Concat(...)
เมื่อคุณเชื่อมต่อโดยใช้ '+' และการนำ Concat เรียก ToString ไปใช้กับแต่ละออบเจ็กต์ที่ส่งผ่านเข้าไป