(this == null) ใน C #!


129

เนื่องจากข้อบกพร่องที่ได้รับการแก้ไขใน C # 4 โปรแกรมต่อไปนี้จะพิมพ์trueออกมา (ลองใช้งานใน LINQPad)

void Main() { new Derived(); }

class Base {
    public Base(Func<string> valueMaker) { Console.WriteLine(valueMaker()); }
}
class Derived : Base {
    string CheckNull() { return "Am I null? " + (this == null); }
    public Derived() : base(() => CheckNull()) { }
}

ใน VS2008 ในโหมด Release มันจะพ่น InvalidProgramException (ในโหมด Debug จะทำงานได้ดี)

ใน VS2010 Beta 2 มันไม่ได้คอมไพล์ (ฉันไม่ได้ลอง Beta 1); ฉันเรียนรู้ว่าวิธีที่ยาก

มีวิธีอื่นในการสร้างthis == nullC # ที่บริสุทธิ์หรือไม่?


3
น่าจะเป็นจุดบกพร่องในคอมไพเลอร์ C # 3.0 ทำงานตามที่ควรใน C # 4.0
Mehrdad Afshari

82
@SLaks: ปัญหาเกี่ยวกับข้อบกพร่องคือคุณสามารถคาดหวังให้พวกเขาได้รับการแก้ไขในบางจุดดังนั้นการค้นหาว่า "มีประโยชน์" อาจไม่ใช่เรื่องฉลาด
AnthonyWJones

6
ขอบคุณ! ไม่รู้เกี่ยวกับ LINQPad มันเย็น!
thorn̈

8
สิ่งนี้มีประโยชน์ในทางใดกันแน่?
Allen Rice

6
ข้อผิดพลาดนี้มีประโยชน์อย่างไร
BlackTigerX

คำตอบ:


73

ข้อสังเกตนี้ถูกโพสต์บน StackOverflow ในคำถามอื่นก่อนหน้านี้ในวันนี้

คำตอบที่ดีของMarc สำหรับคำถามนั้นบ่งชี้ว่าตามข้อมูลจำเพาะ (หัวข้อ 7.5.7) คุณไม่ควรเข้าถึงthisในบริบทนั้นและความสามารถในการทำเช่นนั้นในคอมไพเลอร์ C # 3.0 เป็นข้อบกพร่อง คอมไพเลอร์ C # 4.0 ทำงานอย่างถูกต้องตามข้อมูลจำเพาะ (แม้ในเบต้า 1 นี่เป็นข้อผิดพลาดเวลาคอมไพล์):

§ 7.5.7 การเข้าถึงนี้

นี้การเข้าถึงthisประกอบด้วยคำสงวน

นี้การเข้าถึง:

this

เข้าถึงนี้จะได้รับอนุญาตเฉพาะในบล็อกของตัวสร้างเช่นวิธีการเช่นหรือตัวเข้าถึงอินสแตนซ์


2
ฉันไม่เห็นเหตุใดในรหัสที่แสดงในคำถามนี้การใช้คำหลัก "this" จึงไม่ถูกต้อง วิธี CheckNull เป็นวิธีการอินสแตนซ์ปกติไม่คงที่ การใช้ "this" ใช้ได้ 100% ในวิธีการดังกล่าวและแม้กระทั่งการเปรียบเทียบสิ่งนี้กับ null ก็ใช้ได้ ข้อผิดพลาดอยู่ในบรรทัดเริ่มต้น: เป็นความพยายามที่จะส่งผู้รับมอบสิทธิ์ขอบเขตอินสแตนซ์เป็นพารามิเตอร์ไปยัง ctor ฐาน นี่คือจุดบกพร่อง (รูในการตรวจสอบเซมาติก) ในคอมไพเลอร์ซึ่งไม่น่าจะเป็นไปได้ คุณไม่ได้รับอนุญาตให้เขียน: base(CheckNull())ถ้า CheckNull ไม่ใช่แบบคงที่และคุณไม่ควรใส่แลมด้าที่เชื่อมอินสแตนซ์แบบอินไลน์
quetzalcoatl

4
@quetzalcoatl: thisในCheckNullวิธีการที่ถูกต้องตามกฎหมาย อะไรคือสิ่งที่ไม่ถูกกฎหมายเป็นนัย นี้การเข้าถึงใน() => CheckNull()หลัก() => this.CheckNull()ซึ่งเป็นที่ทำงานนอกบล็อกของตัวสร้างอินสแตนซ์ ฉันยอมรับว่าส่วนหนึ่งของข้อมูลจำเพาะที่ฉันอ้างถึงนั้นส่วนใหญ่มุ่งเน้นไปที่ความถูกต้องตามกฎหมายทางวากยสัมพันธ์ของthisคำสำคัญและอาจเป็นอีกส่วนหนึ่งที่แก้ไขปัญหานี้ได้อย่างแม่นยำมากขึ้น แต่ก็ง่ายต่อการคาดการณ์เชิงแนวคิดจากส่วนนี้ของข้อมูลจำเพาะเช่นกัน
Mehrdad Afshari

2
ขอโทษฉันไม่เห็นด้วย ในขณะที่ฉันรู้ว่า (และเขียนไว้ในความคิดเห็นด้านบน) และคุณก็รู้เช่นกัน - คุณไม่ได้กล่าวถึงสาเหตุที่แท้จริงของปัญหาในคำตอบของคุณ (ยอมรับ) คำตอบได้รับการยอมรับ - ดูเหมือนว่าผู้เขียนจะวาดกราฟด้วยเช่นกัน แต่ฉันสงสัยว่าผู้อ่านทุกคนจะมีความสว่างและคล่องแคล่วใน lambdas ในการจดจำอินสแตนซ์ - แลมด้าเทียบกับแลมด้าแบบคงที่ตั้งแต่แรกเห็นและจับคู่สิ่งนั้นเป็น 'สิ่งนี้' และปัญหาเกี่ยวกับการปล่อย IL :) นี่คือเหตุผลที่ฉันเพิ่มสามเซ็นต์ของฉัน นอกจากนั้นฉันเห็นด้วยกับทุกสิ่งที่พบวิเคราะห์และอธิบายโดยคุณและคนอื่น ๆ :)
quetzalcoatl

24

การถอดรหัสแบบดิบ (ตัวสะท้อนที่ไม่มีการปรับให้เหมาะสม) ของไบนารีโหมดดีบักคือ

private class Derived : Program.Base
{
    // Methods
    public Derived()
    {
        base..ctor(new Func<string>(Program.Derived.<.ctor>b__0));
        return;
    }

    [CompilerGenerated]
    private static string <.ctor>b__0()
    {
        string CS$1$0000;
        CS$1$0000 = CS$1$0000.CheckNull();
    Label_0009:
        return CS$1$0000;
    }

    private string CheckNull()
    {
        string CS$1$0000;
        CS$1$0000 = "Am I null? " + ((bool) (this == null));
    Label_0017:
        return CS$1$0000;
    }
}

วิธี CompilerGenerated ไม่สมเหตุสมผล หากคุณดูที่ IL (ด้านล่าง) จะเรียกใช้เมธอดบนสตริงว่าง(!)

   .locals init (
        [0] string CS$1$0000)
    L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull()
    L_0006: stloc.0 
    L_0007: br.s L_0009
    L_0009: ldloc.0 
    L_000a: ret 

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

    L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull()
    L_0006: ret 

(ตัวสะท้อนแสงขัดข้องเมื่อเปลี่ยนเป็น C #)


แก้ไข : ไม่มีใคร (Eric Lippert?) รู้ว่าทำไมคอมไพเลอร์จึงปล่อยldloc?


11

ฉันมีมัน! (และมีหลักฐานด้วย)

ข้อความแสดงแทน


2
มาช้าเป็นสัญญาณว่าฉันควรหยุดเขียนโค้ด :) แฮ็คของเราด้วยสิ่ง DLR IIRC
leppie

สร้างวิชวลไลเซอร์ดีบักเกอร์ (DebuggerDisplay) สำหรับสิ่งที่ 'นี่' คืออะไรและทำให้สิ่งนั้นหลอกคุณว่าเป็นโมฆะ? : D just sayin '

10

นี่ไม่ใช่ "ข้อบกพร่อง" นี่คือคุณใช้ระบบประเภทในทางที่ผิด คุณไม่ควรส่งการอ้างอิงไปยังอินสแตนซ์ปัจจุบัน ( this) ให้กับใครก็ตามภายในตัวสร้าง

ฉันสามารถสร้าง "จุดบกพร่อง" ที่คล้ายกันได้โดยเรียกใช้วิธีเสมือนภายในตัวสร้างคลาสพื้นฐานเช่นกัน

เพียงเพราะคุณสามารถทำสิ่งที่ไม่ดีไม่ได้หมายความว่ามันจะเป็นจุดบกพร่องเมื่อคุณได้รับมันเล็กน้อย


14
มันเป็นบั๊กของคอมไพเลอร์ มันสร้าง IL ที่ไม่ถูกต้อง (อ่านคำตอบของฉัน)
SLaks

บริบทเป็นแบบคงที่ดังนั้นคุณไม่ควรได้รับอนุญาตให้อ้างอิงวิธีการของอินสแตนซ์ในขั้นตอนนั้น
leppie

10
@ วิล: มันเป็นบั๊กของคอมไพเลอร์ คอมไพเลอร์ควรจะสร้างโค้ดที่ถูกต้องและตรวจสอบได้สำหรับข้อมูลโค้ดนั้นหรือพ่นข้อความแสดงข้อผิดพลาด เมื่อคอมไพเลอร์ไม่ประพฤติตามสเป็คที่มันเป็นรถ
Mehrdad Afshari

2
@ จะ # 4: ตอนที่ฉันเขียนโค้ดฉันไม่ได้คิดเกี่ยวกับผลกระทบ ฉันรู้แค่ว่ามันไม่สมเหตุสมผลเมื่อหยุดรวบรวมใน VS2010 -
SLaks

3
อย่างไรก็ตามการเรียกเมธอดเสมือนในตัวสร้างเป็นการดำเนินการที่ถูกต้องสมบูรณ์ ไม่แนะนำ มันอาจส่งผลให้เกิดภัยพิบัติทางตรรกะ InvalidProgramExceptionแต่ไม่เคย
Mehrdad Afshari

3

ฉันอาจจะคิดผิด แต่ฉันค่อนข้างแน่ใจว่าวัตถุของคุณจะnullไม่มีทางเกิดขึ้นได้thisหรือไม่

ตัวอย่างเช่นคุณจะโทรCheckNullอย่างไร

Derived derived = null;
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException

3
ในแลมบ์ดาในอาร์กิวเมนต์ตัวสร้าง อ่านข้อมูลโค้ดทั้งหมด (และลองดูถ้าคุณไม่เชื่อฉัน)
SLaks

ฉันเห็นด้วยแม้ว่าฉันจะจำบางอย่างได้เล็กน้อยเกี่ยวกับวิธีการใน C ++ วัตถุไม่มีการอ้างอิงภายในตัวสร้างและฉันสงสัยว่าสถานการณ์ (นี้ == null) ถูกใช้ในกรณีเหล่านั้นเพื่อตรวจสอบว่าการเรียกใช้เมธอดนั้นเป็นอย่างไร สร้างขึ้นจากตัวสร้างของวัตถุก่อนที่จะแสดงตัวชี้ไปที่ "this" แม้ว่าเท่าที่ฉันทราบใน C # ไม่ควรมีกรณีใด ๆ ที่ "สิ่งนี้" จะเป็นโมฆะแม้จะอยู่ในวิธี Dispose หรือการสรุป
jpierson

ฉันเดาว่าประเด็นของฉันคือความคิดที่เกิดขึ้นthisไม่ได้ร่วมกันของความเป็นไปได้ที่จะเป็นโมฆะ - ประเภทของ "Cogito, ergo sum" ของการเขียนโปรแกรมคอมพิวเตอร์ ดังนั้นความปรารถนาของคุณที่จะใช้สำนวนthis == nullและมันเคยกลับมาจริงทำให้ฉันเข้าใจผิด
ด่านเต่า

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

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

-1

ไม่แน่ใจว่านี่คือสิ่งที่คุณกำลังมองหา

    public static T CheckForNull<T>(object primary, T Default)
    {
        try
        {
            if (primary != null && !(primary is DBNull))
                return (T)Convert.ChangeType(primary, typeof(T));
            else if (Default.GetType() == typeof(T))
                return Default;
        }
        catch (Exception e)
        {
            throw new Exception("C:CFN.1 - " + e.Message + "Unexpected object type of " + primary.GetType().ToString() + " instead of " + typeof(T).ToString());
        }
        return default(T);
    }

ตัวอย่าง: UserID = CheckForNull (Request.QueryString ["UserID"], 147);


13
คุณเข้าใจคำถามผิดอย่างสิ้นเชิง
SLaks

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