ใน C # จะเกิดอะไรขึ้นเมื่อคุณเรียกใช้เมธอดส่วนขยายบนวัตถุ null


328

มีการเรียกเมธอดด้วยค่า Null หรือให้ข้อยกเว้นการอ้างอิง Null หรือไม่?

MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception?

หากเป็นกรณีนี้ฉันไม่จำเป็นต้องตรวจสอบพารามิเตอร์ 'this' ของฉันอีกเลยใช่ไหม


ยกเว้นกรณีที่คุณกำลังติดต่อกับ ASP.NET MVC ซึ่งจะทำให้เกิดข้อผิดพลาดCannot perform runtime binding on a null referenceนี้
Mrchief

คำตอบ:


386

ที่จะทำงานได้ดี (ไม่มีข้อยกเว้น) วิธีการขยายไม่ใช้การโทรเสมือน (เช่นใช้คำสั่ง "call" il ไม่ใช่ "callvirt") ดังนั้นจึงไม่มีการตรวจสอบค่าว่างนอกจากคุณจะเขียนด้วยตัวคุณเองในวิธีการขยาย นี่เป็นประโยชน์จริง ๆ ในบางกรณี:

public static bool IsNullOrEmpty(this string value)
{
    return string.IsNullOrEmpty(value);
}
public static void ThrowIfNull<T>(this T obj, string parameterName)
        where T : class
{
    if(obj == null) throw new ArgumentNullException(parameterName);
}

ฯลฯ

โดยพื้นฐานแล้วการโทรไปยังสายคงที่นั้นแท้จริงมาก - เช่น

string s = ...
if(s.IsNullOrEmpty()) {...}

กลายเป็น:

string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...}

เห็นได้ชัดว่าไม่มีการตรวจสอบเป็นโมฆะ


1
มาร์คคุณกำลังพูดถึงการโทรแบบ“ เสมือน” แต่ก็เหมือนกันสำหรับการโทรแบบไม่เสมือนบนวิธีการแบบอินสแตนซ์ ฉันคิดว่าคำว่า "เสมือน" ที่นี่ถูกวางผิดที่
Konrad Rudolph

6
@ Konrad: ขึ้นอยู่กับบริบท คอมไพเลอร์ C # มักจะใช้ callvirt แม้สำหรับวิธีที่ไม่เสมือนจริงเพื่อให้ได้รับการตรวจสอบเป็นโมฆะ
Jon Skeet

ฉันหมายถึงความแตกต่างระหว่างคำแนะนำการโทรและ callvirt il หนึ่งในการแก้ไขที่จริงผมพยายามที่จะ href สอง opcodes หน้า แต่บรรณาธิการ barfed ที่เชื่อมโยง ...
Marc Gravell

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

3
@Trap: คุณลักษณะนี้ยอดเยี่ยมหากคุณกำลังเขียนโปรแกรมสไตล์การใช้งาน
Roy Tinker

50

นอกเหนือจากคำตอบที่ถูกต้องจาก Marc Gravell

คุณสามารถรับคำเตือนจากคอมไพเลอร์หากเห็นได้ชัดว่าอาร์กิวเมนต์นี้เป็นโมฆะ:

default(string).MyExtension();

ทำงานได้ดีที่รันไทม์ "Expression will always cause a System.NullReferenceException, because the default value of string is null"แต่ผลิตเตือน


32
ทำไมมันจะเตือน "ทำให้เกิด System.NullReferenceException" เสมอ เมื่อในความเป็นจริงมันจะไม่?
tpower

46
โชคดีที่เราโปรแกรมเมอร์เพียง แต่ใส่ใจกับข้อผิดพลาดเท่านั้นไม่ใช่คำเตือน: p
JulianR

7
@JulianR: ใช่บางคนทำบางคนไม่ได้ ในการกำหนดค่ารุ่นบิลด์ของเราเราถือว่าคำเตือนเป็นข้อผิดพลาด ดังนั้นมันจึงไม่ทำงาน
Stefan Steinegger

8
ขอบคุณสำหรับบันทึก; ฉันจะได้รับสิ่งนี้ในฐานข้อมูลข้อผิดพลาดและเราจะดูว่าเราสามารถแก้ไขได้สำหรับ C # 4.0 (ไม่มีสัญญา - เพราะมันเป็นกรณีที่มุมที่ไม่สมจริงและเป็นเพียงการแจ้งเตือนเราอาจถ่อในการแก้ไขมัน.)
เอริค Lippert

3
@Stefan: เนื่องจากเป็นข้อผิดพลาดและไม่ใช่คำเตือน "ของจริง" คุณสามารถใช้คำสั่ง #pragma เพื่อระงับคำเตือนเพื่อรับรหัสผ่านรหัสงานสร้างของคุณ
Martin RL

24

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

public static class StringNullExtensions { 
  public static bool IsNullOrEmpty(this string s) { 
    return string.IsNullOrEmpty(s); 
  } 
  public static bool IsNullOrBlank(this string s) { 
    return s == null || s.Trim().Length == 0; 
  } 
}

ฉันเคยเขียนบล็อกโพสต์เกี่ยวกับเรื่องนี้เมื่อไม่นานมานี้


3
โหวตสิ่งนี้เพราะมันถูกต้องและสมเหตุสมผลสำหรับฉัน (และเป็นลายลักษณ์อักษร) ในขณะที่ฉันชอบการใช้ที่อธิบายไว้ในคำตอบโดย @Marc Gravell
qxotk

17

null จะถูกส่งผ่านไปยังวิธีการขยาย

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

ผู้ชายที่นี่เขียนวิธีการขยาย "IsNull" และ "IsNotNull" ที่ตรวจสอบว่าการอ้างอิงนั้นผ่านไปแล้วหรือไม่ โดยส่วนตัวฉันคิดว่านี่เป็นความผิดปกติและไม่ควรเห็นแสงของวัน แต่มันใช้ได้อย่างสมบูรณ์แบบ c #


18
สำหรับฉันแล้วมันเหมือนกับการขอศพ "คุณยังมีชีวิตอยู่" และได้รับคำตอบว่า "ไม่" ศพไม่สามารถตอบคำถามใด ๆ ได้คุณไม่ควร "เรียก" วิธีการในวัตถุที่ไม่มีค่า
Binary Worrier

14
ผมไม่เห็นด้วยกับตรรกะ Binary Worrier ฯ ตามที่มันเป็นประโยชน์ความสามารถในการขยายการโทรโดยไม่ต้องกังวลเกี่ยวกับ refs null แต่ +1 สำหรับค่าตลกคล้ายคลึง :-)
ทิมอาเบล

10
ที่จริงแล้วบางครั้งคุณไม่รู้ว่ามีใครตายหรือไม่ดังนั้นคุณยังคงถามและคนนั้นอาจตอบว่า "ไม่เพียงแค่หลับตาหลับตา"
nurchi

7
เมื่อคุณจำเป็นต้องเชื่อมโยงการทำงานหลายอย่าง (เช่น 3+) คุณสามารถ (สมมติว่าไม่มีผลข้างเคียง) เปลี่ยนรหัสตรวจสอบที่น่าเบื่อของสตีมเพลทหลายบรรทัดให้กลายเป็นซับในหนึ่งซับด้วยวิธีการขยายที่ปลอดภัย (คล้ายกับคำแนะนำ ".?" - โอเปอเรเตอร์ แต่ไม่ยอมรับอย่างสง่างาม) หากไม่ชัดเจนส่วนขยายคือ "โมฆะปลอดภัย" ฉันมักจะนำหน้าเมธอดด้วย "ปลอดภัย" ดังนั้นหากเป็นตัวอย่าง วิธีการชื่อของมันอาจเป็น "SafeCopy" และมันจะกลับมาเป็นโมฆะถ้าอาร์กิวเมนต์เป็นโมฆะ
AnorZaken

3
ฉันหัวเราะอย่างหนักด้วย @BinaryWorrier คำตอบฮ่าฮ่าฮ่าฮ่าฉันเห็นตัวเองเตะร่างเพื่อตรวจสอบว่ามันตายหรือไม่ฮ่าฮ่าฮ่าดังนั้นในจินตนาการของฉันใครตรวจสอบว่าร่างกายตายหรือไม่ไม่ใช่ฉันและไม่ใช่ตัวเอง ตรวจสอบอยู่ในฉัน activelly เตะมันเพื่อดูว่ามันจะย้าย ดังนั้นร่างกายไม่ทราบว่ามันตายหรือไม่ใครตรวจสอบรู้ตอนนี้คุณสามารถยืนยันว่าคุณสามารถ "ปลั๊กอิน" กับร่างกายวิธีที่จะบอกคุณว่ามันตายหรือไม่และในความคิดของฉันคืออะไร นามสกุลสำหรับ
Zorkind

7

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

คุณสามารถอ่านบทความนี้สำหรับตัวอย่าง: วิธีการลดความซับซ้อนของวัฏจักร: ข้อที่รุ่นสั้นคือ:

public static class StringExtensions
{
    public static void AssertNonEmpty(this string value, string paramName)
    {
        if (string.IsNullOrEmpty(value))
            throw new ArgumentException("Value must be a non-empty string.", paramName);
    }
}

นี่คือวิธีการขยายคลาสสตริงซึ่งสามารถเรียกใช้ในการอ้างอิงเป็นโมฆะ:

((string)null).AssertNonEmpty("null");

การโทรทำงานได้ดีเพียงเพราะรันไทม์จะเรียกวิธีการส่วนขยายในการอ้างอิงเป็นค่าว่าง จากนั้นคุณสามารถใช้วิธีการขยายนี้เพื่อใช้คำสั่งป้องกันโดยไม่มีไวยากรณ์ยุ่ง:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

        userName.AssertNonEmpty("userName");
        referrerName.AssertNonEmpty("referrerName");

        ...

    }

3

วิธีการขยายคงที่ดังนั้นถ้าคุณไม่ทำอะไรกับ MyObject นี้มันไม่น่าจะมีปัญหาการทดสอบอย่างรวดเร็วควรตรวจสอบมัน :)


-1

มีกฎทองเล็กน้อยเมื่อคุณต้องการให้คุณอ่านและเป็นแนวตั้ง

  • สิ่งหนึ่งที่ควรค่าแก่การพูดจากหอไอเฟลกล่าวว่าโค้ดเฉพาะที่ห่อหุ้มไว้ในเมธอดควรทำงานกับอินพุตบางส่วนโค้ดนั้นสามารถใช้งานได้หากตรงตามเงื่อนไขและรับประกันผลลัพธ์ที่คาดหวัง

ในกรณีของคุณ - DesignByContract ใช้งานไม่ได้ ... คุณจะต้องใช้ตรรกะบางอย่างในอินสแตนซ์ที่เป็นโมฆะ

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