null + true เป็นสตริงอย่างไร


112

เนื่องจากtrueไม่ใช่ประเภทnull + trueสตริงสตริงเป็นอย่างไร

string s = true;  //Cannot implicitly convert type 'bool' to 'string'   
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'

เหตุผลเบื้องหลังนี้คืออะไร?


27
คุณทำอะไรที่ไม่สมเหตุสมผลแล้วไม่ชอบข้อความที่คอมไพเลอร์สร้างขึ้นมา? นี่คือรหัส C # ไม่ถูกต้อง ... คุณต้องการให้ทำอย่างไร
Hogan

19
@Hogan: รหัสนี้ดูสุ่มและเป็นวิชาการเพียงพอสำหรับคำถามที่ถามจากความอยากรู้อยากเห็นล้วนๆ แค่เดา ​​...
BoltClock

8
null + true คืนค่า 1 ใน JavaScript
Fatih Acet

คำตอบ:


147

สิ่งนี้อาจดูแปลกประหลาดเพียงทำตามกฎจากข้อกำหนดภาษา 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แต่ในกรณีนี้ความล้มเหลวในการแปลงเป็นสตริงทำให้นิพจน์ทั้งหมดเป็นข้อผิดพลาดและด้วยเหตุนี้จึงไม่มีประเภท
leppie

6
@leppie: การแสดงออกเป็นที่ถูกต้องและชนิดของมันคือสตริง ลอง "var x = null + true;" - มันรวบรวมและเป็นประเภทx stringหมายเหตุว่าลายเซ็นที่ใช้ที่นี่เป็นstring operator+(string, object)- ก็แปลงboolไปobject(ซึ่งเป็นดี) stringไม่ได้ที่จะ
Jon Skeet

ขอบคุณจอนเข้าใจแล้ว BTW ส่วน 14.7.4 ในเวอร์ชัน 2 ของข้อมูลจำเพาะ
leppie

@leppie: นั่นอยู่ในเลข ECMA หรือเปล่า? มันแตกต่างกันมากเสมอ :(
Jon Skeet

47
ใน 20 นาที เขาเขียนสิ่งนี้ทั้งหมดใน 20 นาที เขาควรจะเขียนหนังสือหรือบางอย่าง ... เดี๋ยวนะ
Epaga

44

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

string operator +(string x, object y)

null + trueเกินนี้เข้ากันได้กับประเภทการโต้แย้งในการแสดงออก ดังนั้นมันจะถูกเลือกเป็นผู้ดำเนินการและได้รับการประเมินเป็นหลักซึ่งจะประเมินค่า((string)null) + true"True"

ส่วน 7.7.4 ของข้อกำหนดภาษา C # มีรายละเอียดเกี่ยวกับความละเอียดนี้


1
ฉันสังเกตเห็นว่า IL ที่สร้างขึ้นนั้นตรงไปที่การโทรหา Concat ฉันคิดว่าฉันจะเห็นการเรียกไปยังฟังก์ชันตัวดำเนินการ + อยู่ในนั้น นั่นจะเป็นการเพิ่มประสิทธิภาพภายในหรือไม่?
quentin-star ใน

1
@qstarin ผมเชื่อว่าเหตุผลก็คือว่าไม่มีจริงๆสำหรับoperator+ stringแต่มันมีอยู่ในใจของคอมไพเลอร์เท่านั้นและมันก็แปลเป็นคำเรียกร้องstring.Concat
JaredPar

1
@qstarin @JaredPar: ฉันได้ตอบคำถามไปอีกเล็กน้อย
Jon Skeet

11

คอมไพเลอร์ออกตามล่าหาตัวดำเนินการ + () ที่สามารถใช้อาร์กิวเมนต์ว่างก่อน ไม่มีชนิดของค่ามาตรฐานที่มีคุณสมบัติเป็นโมฆะไม่ใช่ค่าที่ถูกต้องสำหรับพวกเขา การจับคู่หนึ่งเดียวคือ System.String.operator + () ไม่มีความคลุมเครือ

อาร์กิวเมนต์ที่ 2 ของตัวดำเนินการนั้นยังเป็นสตริง ที่ไป kapooey ไม่สามารถแปลงบูลเป็นสตริงโดยปริยาย


10

ที่น่าสนใจคือการใช้ตัวสะท้อนแสงเพื่อตรวจสอบสิ่งที่สร้างขึ้นรหัสต่อไปนี้:

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;เป็นจริงไม่ได้รับการยอมรับจากคอมไพเลอร์


8

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

5
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...


5

เหตุผลนี้คือความสะดวก (การต่อสตริงเป็นงานทั่วไป)

ดังที่ BoltClock กล่าวไว้ตัวดำเนินการ '+' ถูกกำหนดตามประเภทตัวเลขสตริงและสามารถกำหนดสำหรับประเภทของเราเองได้เช่นกัน (ตัวดำเนินการมากเกินไป)

หากไม่มีตัวดำเนินการ '+' ที่โอเวอร์โหลดในประเภทของอาร์กิวเมนต์และไม่ใช่ประเภทตัวเลขคอมไพเลอร์จะตั้งค่าเริ่มต้นเป็นการต่อสายอักขระ

คอมไพเลอร์แทรกการเรียกString.Concat(...)เมื่อคุณเชื่อมต่อโดยใช้ '+' และการนำ Concat เรียก ToString ไปใช้กับแต่ละออบเจ็กต์ที่ส่งผ่านเข้าไป

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