เหตุใดจึงถูกต้องที่จะเชื่อมต่อสตริง Null แต่ไม่สามารถเรียก "null ToString ()" ได้


126

นี่คือรหัส C # ที่ถูกต้อง

var bob = "abc" + null + null + null + "123";  // abc123

นี่ไม่ใช่รหัส C # ที่ถูกต้อง

var wtf = null.ToString(); // compiler error

เหตุใดคำสั่งแรกจึงใช้ได้


97
ฉันคิดว่ามันแปลกที่คุณจะได้รับชื่อnull.ToString() wtfทำไมสิ่งนั้นทำให้คุณประหลาดใจ? คุณไม่สามารถเรียกเมธอดอินสแตนซ์ได้เมื่อคุณไม่มีอะไรจะเรียกมันตั้งแต่แรก
BoltClock

11
@BoltClock: แน่นอนว่าเป็นไปได้ที่จะเรียกวิธีการอินสแตนซ์บนอินสแตนซ์ว่าง ไม่สามารถทำได้ใน C # แต่ใช้ได้กับ CLR :)
leppie

1
คำตอบเกี่ยวกับ String.Concat เกือบถูกต้อง ในความเป็นจริงตัวอย่างเฉพาะในคำถามคือหนึ่งในการพับแบบคงที่และค่าว่างในบรรทัดแรกจะถูกตัดออกโดยคอมไพเลอร์นั่นคือพวกมันจะไม่ถูกประเมินที่รันไทม์เพราะไม่มีอยู่อีกต่อไป - คอมไพเลอร์ได้ลบออก ผมเขียนคนเดียวเล็ก ๆ น้อย ๆ ในทุกกฎสำหรับการเรียงต่อกันและคงพับของสตริงมากกว่าที่นี่เพื่อดูข้อมูลเพิ่มเติมได้ที่: stackoverflow.com/questions/9132338/...
Chris Shain

77
คุณไม่สามารถเพิ่มอะไรลงในสตริงได้ แต่คุณไม่สามารถสร้างสตริงจากอะไรได้
RGB

คำสั่งที่สองไม่ถูกต้องหรือไม่? class null_extension { String ToString( Object this arg ) { return ToString(arg); } }
ctrl-alt-delor

คำตอบ:


163

เหตุผลในการทำงานครั้งแรก:

จากMSDN :

ในการดำเนินการต่อสายอักขระคอมไพลเลอร์ C # จะถือว่าสตริงว่างเหมือนกับสตริงว่าง แต่จะไม่แปลงค่าของสตริง null ดั้งเดิม

ข้อมูลเพิ่มเติมเกี่ยวกับตัวดำเนินการ+ ไบนารี :

ตัวดำเนินการ binary + ทำการต่อสายอักขระเมื่อตัวถูกดำเนินการหนึ่งหรือทั้งสองตัวเป็นสตริงชนิด

ถ้าตัวถูกดำเนินการของการต่อสายอักขระเป็นโมฆะสตริงว่างจะถูกแทนที่ มิฉะนั้นอาร์กิวเมนต์ที่ไม่ใช่สตริงจะถูกแปลงเป็นการแสดงสตริงโดยเรียกใช้ToStringเมธอดเสมือนที่สืบทอดมาจากอ็อบเจ็กต์ประเภท

หากToStringส่งคืนnullสตริงว่างจะถูกแทนที่

สาเหตุของข้อผิดพลาดในวินาทีคือ:

null (การอ้างอิง C #) - คีย์เวิร์ด null คือลิเทอรัลที่แสดงถึงการอ้างอิง null ซึ่งไม่ได้อ้างถึงอ็อบเจ็กต์ใด ๆ null คือค่าเริ่มต้นของตัวแปรชนิดอ้างอิง


1
จะได้ผลหรือไม่ var wtf = ((String)null).ToString();ฉันทำงานใน Java เมื่อเร็ว ๆ นี้ซึ่งเป็นไปได้ที่การแคสต์ null เป็นไปได้ระยะหนึ่งแล้วที่ฉันทำงานกับ C #
Jochem

14
@Jochem: คุณยังคงพยายามเรียกเมธอดบนวัตถุว่างดังนั้นฉันเดาว่าไม่ คิดว่ามันเป็นVSnull.ToString() ToString(null)
Svish

@Svish ใช่ตอนนี้ฉันคิดอีกครั้งมันเป็นวัตถุว่างดังนั้นคุณพูดถูกมันจะไม่ทำงาน มันจะไม่อยู่ใน Java ไม่ใช่: ข้อยกเว้นตัวชี้ null ไม่เป็นไร. Tnx สำหรับคำตอบของคุณ! [แก้ไข: ทดสอบใน Java: NullPointerException ด้วยความแตกต่างที่มันรวบรวมโดยไม่ต้องแคสต์มันไม่ได้]
Jochem

โดยทั่วไปแล้วไม่มีเหตุผลใดที่จะจงใจเชื่อมต่อหรือ ToString คีย์เวิร์ด null หากคุณต้องการสตริงว่างให้ใช้สตริงว่าง หากคุณต้องการตรวจสอบว่าตัวแปรสตริงเป็นโมฆะหรือไม่คุณสามารถใช้ (myString == null) หรือ string.IsNullOrEmpty (myString) อีกวิธีหนึ่งในการแปลงตัวแปรสตริง null เป็นสตริงว่างใช้ myNewString = (myString == null ?? string.Empty)
csauve

@Jochem หล่อมันจะทำให้คอมไพล์ แต่แน่นอนมันจะโยน NullReferenceException ที่รันไทม์
jeroenh

108

เนื่องจากตัว+ดำเนินการใน C # แปลเป็นการภายในString.Concatซึ่งเป็นวิธีการคงที่ และวิธีนี้จะถือว่าnullเหมือนกับสตริงว่างเปล่า หากคุณดูแหล่งที่มาของString.Concatในตัวสะท้อนแสงคุณจะเห็น:

// while looping through the parameters
strArray[i] = (str == null) ? Empty : str;
// then concatenate that string array

(MSDN กล่าวถึงเช่นกัน: http://msdn.microsoft.com/en-us/library/k9c94ey1.aspx )

ในทางกลับกันToString()เป็นวิธีการอินสแตนซ์ที่คุณไม่สามารถเรียกใช้ได้null(ควรใช้สำหรับประเภทnullใด)


2
ไม่มีตัวดำเนินการ + ในคลาส String ว่า .. คอมไพลเลอร์แปลเป็น String.Concat หรือไม่?
superlogical

@superlogical ใช่นั่นคือสิ่งที่คอมไพเลอร์ทำ ดังนั้นจริงที่+ผู้ประกอบการในสายใน C # String.Concatเป็นเพียงน้ำตาลประโยคสำหรับ
Botz3000

การเพิ่มเข้าไป: คอมไพเลอร์จะเลือก Concat ที่มากเกินไปโดยอัตโนมัติ โอเวอร์โหลดที่ใช้ได้คือพารามิเตอร์อ็อบเจ็กต์ 1, 2 หรือ 3 พารามิเตอร์อ็อบเจ็กต์ 4 ตัว + __arglistและparamsเวอร์ชันอาร์เรย์อ็อบเจ็กต์
Wormbo

มีการแก้ไขสตริงอาร์เรย์อะไร ไม่Concatเสมอสร้างอาร์เรย์ใหม่ของสตริง null ไม่ใช่แม้กระทั่งเมื่อได้รับอาร์เรย์ของสตริง null ไม่ใช่หรือเป็นอย่างอื่นที่เกิดขึ้น?
supercat

@supercat อาร์เรย์ที่แก้ไขคืออาร์เรย์ใหม่ซึ่งจะถูกส่งต่อไปยังเมธอดผู้ช่วยส่วนตัว คุณสามารถดูรหัสด้วยตัวเองโดยใช้ตัวสะท้อนแสง
Botz3000

29

ตัวอย่างแรกจะถูกแปลเป็น:

var bob = String.Concat("abc123", null, null, null, "abs123");

Concatการป้อนข้อมูลวิธีการตรวจสอบและแปล null เป็นสตริงที่ว่างเปล่า

วินาทีตัวอย่างจะถูกแปลเป็น:

var wtf = ((object)null).ToString();

ดังนั้นnullข้อยกเว้นการอ้างอิงจะถูกสร้างขึ้นที่นี่


ที่จริงAccessViolationExceptionจะถูกโยน :)
leppie

ฉันเพิ่งทำ :) ((object)null).ToString()=> AccessViolation ((object)1 as string).ToString()=>NullReference
leppie

1
ฉันมี NullReferenceException DN 4.0
Viacheslav Smityukh

น่าสนใจ เมื่อฉันใส่((object)null).ToString()เข้าไปtry/catchฉันNullReferenceExceptionก็เกินไป
leppie

3
@Ieppie ยกเว้นว่าจะไม่โยน AccessViolation ด้วย (ทำไมถึงเป็นเช่นนั้น) - NullReferenceException ที่นี่
jeroenh

11

ส่วนแรกของรหัสของคุณได้รับการปฏิบัติเช่นเดียวกับที่ในString.Concat,

ซึ่งเป็นสิ่งที่คอมไพเลอร์ C # เรียกเมื่อคุณเพิ่มสตริง " abc" + nullได้รับการแปลเป็นภาษาString.Concat("abc", null),

และภายในวิธีการที่จะแทนที่ด้วยnull String.Emptyนั่นเป็นเหตุผลว่าทำไมโค้ดส่วนแรกของคุณจึงไม่มีข้อยกเว้นใด ๆ มันก็เหมือนกับ

var bob = "abc" + string.Empty + string.Empty + string.Empty + "123";  //abc123

และในส่วนที่ 2 ของโค้ดของคุณจะแสดงข้อยกเว้นเนื่องจาก 'null' ไม่ใช่อ็อบเจ็กต์คีย์เวิร์ด null คือลิเทอรัลที่แสดงถึงการอ้างอิงที่เป็นโมฆะซึ่งไม่ได้อ้างถึงอ็อบเจ็กต์ใด ๆ null คือค่าเริ่มต้นของตัวแปรชนิดอ้างอิง

และ ' ToString()' คือเมธอดที่สามารถเรียกใช้โดยอินสแตนซ์ของอ็อบเจกต์ แต่ไม่ใช่ตัวอักษรใด ๆ


3
String.Emptyไม่เท่ากับnull. มันได้รับการปฏิบัติในลักษณะเดียวกันในบางวิธีเช่นString.Concat. ตัวอย่างเช่นหากคุณตั้งค่าตัวแปรสตริงเป็นnullC # จะไม่แทนที่ด้วยString.Emptyหากคุณพยายามเรียกใช้เมธอด
Botz3000

3
เกือบจะ nullไม่ได้รับการปฏิบัติเช่นเดียวกับString.EmptyC # แต่ก็ถือว่าเป็นเช่นนั้นString.Concatซึ่งเป็นสิ่งที่คอมไพเลอร์ C # เรียกเมื่อคุณเพิ่มสตริง "abc" + nullได้รับการแปลเป็นภาษาString.Concat("abc", null)และภายในที่มาแทนที่วิธีการด้วยnull String.Emptyส่วนที่สองถูกต้องสมบูรณ์
Botz3000

9

ในเฟรมเวิร์ก COM ที่นำหน้า. net จำเป็นสำหรับรูทีนใด ๆ ที่ได้รับสตริงเพื่อให้เป็นอิสระเมื่อดำเนินการเสร็จสิ้น เนื่องจากเป็นเรื่องปกติมากที่สตริงว่างจะถูกส่งเข้าและออกจากรูทีนและเนื่องจากการพยายาม "ว่าง" ตัวชี้ค่าว่างจึงถูกกำหนดให้เป็นการดำเนินการที่ไม่ต้องทำอะไรที่ถูกต้อง Microsoft จึงตัดสินใจให้ตัวชี้สตริงว่างแทนสตริงว่าง

เพื่อให้เข้ากันได้กับ COM บางรูทีนใน. net จะตีความอ็อบเจ็กต์ null เป็นการแสดงทางกฎหมายเป็นสตริงว่าง ด้วยการเปลี่ยนแปลงเล็กน้อย. net และภาษาของมัน (โดยเฉพาะอย่างยิ่งการอนุญาตให้สมาชิกอินสแตนซ์ระบุว่า "ไม่เรียกใช้เสมือน") Microsoft อาจทำให้nullอ็อบเจ็กต์ประเภท String ที่ประกาศทำงานได้ดียิ่งขึ้นเช่นสตริงว่าง หาก Microsoft ทำเช่นนั้นก็จะต้องทำให้Nullable<T>งานแตกต่างกันไปบ้าง (เพื่อให้อนุญาต - Nullable<String>สิ่งที่ IMHO ควรทำต่อไป) และ / หรือกำหนดNullableStringประเภทซึ่งส่วนใหญ่จะใช้แทนกันได้Stringแต่จะไม่คำนึงถึงnullเป็นสตริงว่างที่ถูกต้อง

ตามที่เป็นอยู่มีบริบทบางอย่างที่nullจะถือว่าa จะถือเป็นสตริงว่างเปล่าที่ถูกต้องและอื่น ๆ ที่จะไม่ใช้ ไม่ใช่สถานการณ์ที่เป็นประโยชน์มากนัก แต่เป็นสิ่งที่โปรแกรมเมอร์ควรตระหนัก โดยทั่วไปนิพจน์ของฟอร์มstringValue.someMemberจะล้มเหลวถ้าstringValueเป็นnullแต่เมธอดเฟรมเวิร์กและตัวดำเนินการส่วนใหญ่ที่ยอมรับสตริงเป็นพารามิเตอร์จะถือว่าnullเป็นสตริงว่าง


5

'+'เป็นตัวดำเนินการ infix เช่นเดียวกับโอเปอเรเตอร์ใด ๆ มันกำลังเรียกใช้เมธอด คุณสามารถจินตนาการถึงเวอร์ชันที่ไม่ต่อเนื่อง"wow".Plus(null) == "wow"

ผู้ดำเนินการได้ตัดสินใจในสิ่งนี้ ...

class String
{
  ...
  String Plus(ending)
  {
     if(ending == null) return this;
     ...
  }
} 

ดังนั้น .. ตัวอย่างของคุณจึงกลายเป็น

var bob = "abc".Plus(null).Plus(null).Plus(null).Plus("123");  // abc123

ซึ่งเหมือนกับ

var bob = "abc".Plus("123");  // abc123

ไม่มีจุดว่างจะกลายเป็นสตริง ดังนั้นไม่แตกต่างกันว่าnull.ToString() null.VoteMyAnswer();)


var bob = Plus(Plus(Plus(Plus("abc",null),null),null),"123");จริงๆก็มากขึ้นเช่น Operator overloads เป็นวิธีการคงที่ในหัวใจของพวกเขา: msdn.microsoft.com/en-us/library/s53ehcz3(v=VS.71).aspx ใช่var bob = null + "abc";หรือไม่หรือโดยเฉพาะอย่างยิ่งstring bob = null + null;จะไม่ถูกต้อง
Ben Mosher

คุณพูดถูกฉันแค่รู้สึกว่าฉันจะสับสนเกินไปถ้าฉันอธิบายแบบนั้น
Nigel Thorne


3

มีคนพูดในหัวข้อสนทนานี้ว่าคุณไม่สามารถสร้างสตริงออกมาได้ (ซึ่งเป็นวลีที่ดีอย่างที่ฉันคิด) แต่ใช่คุณสามารถ :-) ได้ดังตัวอย่างต่อไปนี้:

var x = null + (string)null;     
var wtf = x.ToString();

ทำงานได้ดีและไม่เกิดข้อยกเว้นเลย ข้อแตกต่างเพียงอย่างเดียวคือคุณต้องแคสต์ null หนึ่งในสตริง - หากคุณลบ(สตริง)การแคสต์ตัวอย่างจะยังคงคอมไพล์ แต่มีข้อยกเว้นรันไทม์: "Operator '+' มีความคลุมเครือในตัวดำเนินการของ พิมพ์ '<null>' และ '<null>' "

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


ข้อเท็จจริงที่น่าสนใจอีกประการหนึ่งก็คือใน C # / .NET วิธีnullการปฏิบัติจะไม่เหมือนกันเสมอไปหากคุณพิจารณาประเภทข้อมูลที่แตกต่างกัน ตัวอย่างเช่น:

int? x = 1;  //  string x = "1";
x = x + null + null;
Console.WriteLine((x==null) ? "<null>" : x.ToString());

เกี่ยวกับบรรทัดที่ 1ของข้อมูลโค้ด: ถ้าxเป็นตัวแปรจำนวนเต็มว่าง (เช่นint?) ที่มีค่า1คุณจะได้รับผลลัพธ์<null>กลับมา ถ้ามันเป็นสตริง (ดังแสดงในความคิดเห็น) ที่มีค่า"1"แล้วคุณจะได้รับกลับมามากกว่า"1"<null>

หมายเหตุน่าสนใจเช่นกัน: หากคุณใช้var x = 1;สำหรับบรรทัดแรกแสดงว่าคุณได้รับข้อผิดพลาดรันไทม์ ทำไม? เนื่องจากการกำหนดจะเปลี่ยนตัวแปรxให้เป็นประเภทข้อมูลintซึ่งไม่สามารถกำหนดค่าว่างได้ คอมไพเลอร์ไม่ได้ถือว่าint?ที่นี่และด้วยเหตุนี้จึงล้มเหลวในบรรทัดที่ 2 เมื่อnullมีการเพิ่ม


2

การเพิ่มnullลงในสตริงจะถูกละเว้น null(ในตัวอย่างที่สองของคุณ) ไม่ใช่ตัวอย่างของวัตถุใด ๆ ดังนั้นจึงไม่มีToString()วิธีการ มันเป็นเพียงตัวอักษร


1

เนื่องจากไม่มีความแตกต่างระหว่างstring.Emptyและnullเมื่อคุณต่อสตริง คุณสามารถส่งค่า null เข้าไปได้string.Formatเช่นกัน แต่คุณกำลังพยายามเรียกใช้เมธอดnullซึ่งจะส่งผลให้NullReferenceExceptionเกิดข้อผิดพลาดของคอมไพเลอร์เสมอ
หากมีเหตุผลบางอย่างที่คุณอยากจะทำมันคุณสามารถเขียนวิธีขยายการตรวจสอบว่าแล้วผลตอบแทนnull string.Emptyแต่ควรใช้ส่วนขยายเช่นนั้นเมื่อจำเป็นเท่านั้น (ในความคิดของฉัน)


0

โดยทั่วไป: มันอาจถูกต้องหรือไม่ก็ได้ที่ยอมรับ null เป็นพารามิเตอร์ขึ้นอยู่กับข้อกำหนด แต่การเรียกใช้เมธอดเป็นโมฆะไม่ถูกต้องเสมอไป


นั่นคือหัวข้ออื่น ๆ ที่ว่าทำไมโอเปอเรเตอร์ของตัวดำเนินการ + จึงเป็นโมฆะในกรณีของสตริง นี่เป็นสิ่งที่ VB (ขออภัย) เพื่อทำให้ชีวิตโปรแกรมเมอร์ง่ายขึ้นหรือสมมติว่าโปรแกรมเมอร์ไม่สามารถจัดการกับ null ได้ ฉันไม่เห็นด้วยกับข้อกำหนดนี้อย่างสิ้นเชิง 'ไม่รู้' + 'อะไร' ควรยัง 'ไม่ทราบ' ...

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