อะไรคือความแตกต่างระหว่าง“ x is null” และ“ x == null”?


276

ใน C # 7 เราสามารถใช้

if (x is null) return;

แทน

if (x == null) return;

มีข้อได้เปรียบอะไรบ้างในการใช้วิธีการใหม่ (ตัวอย่างก่อนหน้า) ในแบบเก่า?

ความหมายต่างกันหรือไม่?

เป็นเรื่องของรสนิยมหรือไม่? ถ้าไม่ฉันควรใช้อันใดอันหนึ่งกับอีกอันหนึ่ง?

อ้างอิง: มีอะไรใหม่ใน C # 7.0


4
นั่นคือลิงค์ที่ฉันเพิ่งดู แต่มันก็ไม่ได้ให้ข้อมูลอะไรมากมายกับคุณซึ่งเป็นสาเหตุที่ฉันเดาว่า OP กำลังถามคำถาม ส่วนที่สำคัญที่สุดของหน้านี้คือการทดสอบนี้คือโอเปอเรเตอร์ "โอเปอเรเตอร์" คือ "ใช้ในการตรวจสอบว่าประเภทของวัตถุรันไทม์เข้ากันได้กับชนิดที่กำหนดหรือไม่ กล่าวอีกนัยหนึ่งเราใช้ตัวดำเนินการ "is" เพื่อตรวจสอบว่าชนิดของวัตถุนั้นเป็นสิ่งที่เราคาดหวัง ลองดูไวยากรณ์ของมัน:
Simon Price

2
@SimonPrice ที่เกี่ยวกับรุ่นปัจจุบันของ C #: C # 6 คำถามนี้เป็นคำถามเกี่ยวกับ C # 7 ซึ่งมีการจับคู่แบบ
แพทริคฮอฟแมน

@bigown คุณกำลังมองหารายละเอียดชนิดใด?
Patrick Hofman

@PatrickHofman ประเภทของ svick ตอบโดยยกตัวอย่าง
Maniero

คำตอบ:


232

ปรับปรุง:โรสลินคอมไพเลอร์ที่ได้รับการปรับปรุงเพื่อให้การทำงานของสองผู้ประกอบการเดียวกันเมื่อไม่มีผู้ประกอบการเท่าเทียมกันมากเกินไป โปรดดูรหัสในผลลัพธ์คอมไพเลอร์ปัจจุบัน ( M1และM2ในรหัส) ที่แสดงสิ่งที่เกิดขึ้นเมื่อไม่มีการเปรียบเทียบความเท่าเทียมกันมากเกินไป ตอนนี้พวกเขาทั้งสองมี==พฤติกรรมที่มีประสิทธิภาพดีกว่า หากมีความเท่าเทียมกัน Comparer เกินไปที่แตกต่างยังคงรหัส

ดูการคอมไพเลอร์ Roslyn รุ่นเก่าได้จากการวิเคราะห์ด้านล่าง


เพราะnullไม่มีความแตกต่างกับสิ่งที่เราคุ้นเคยกับ C # 6 อย่างไรก็ตามสิ่งที่น่าสนใจเมื่อคุณเปลี่ยนnullเป็นค่าคงที่อื่น

ยกตัวอย่างเช่น

Test(1);

public void Test(object o)
{
    if (o is 1) Console.WriteLine("a");
    else Console.WriteLine("b");
}

aอัตราผลตอบแทนการทดสอบ หากคุณเปรียบเทียบo == (object)1สิ่งนั้นกับสิ่งที่คุณเขียนตามปกติมันจะสร้างความแตกต่าง isคำนึงถึงประเภทของอีกด้านหนึ่งของการเปรียบเทียบ มันเจ๋งมาก!

ฉันคิดว่ารูปแบบ== nullvs. is nullคงที่เป็นเพียงสิ่งที่คุ้นเคย 'โดยไม่ได้ตั้งใจ' ซึ่งไวยากรณ์ของตัวisดำเนินการและตัวดำเนินการเท่ากับให้ผลลัพธ์เหมือนกัน


ในฐานะที่เป็นsvickความเห็นis nullสายSystem.Object::Equals(object, object)ที่==ceqโทร

IL สำหรับis:

IL_0000: ldarg.1              // Load argument 1 onto the stack
IL_0001: ldnull               // Push a null reference on the stack
IL_0002: call bool [mscorlib]System.Object::Equals(object, object) // Call method indicated on the stack with arguments
IL_0007: ret                  // Return from method, possibly with a value

IL สำหรับ==:

IL_0000: ldarg.1              // Load argument 1 onto the stack
IL_0001: ldnull               // Push a null reference on the stack
IL_0002: ceq                  // Push 1 (of type int32) if value1 equals value2, else push 0
IL_0004: ret                  // Return from method, possibly with a value

เนื่องจากเรากำลังพูดถึงnullไม่มีความแตกต่างเนื่องจากสิ่งนี้สร้างความแตกต่างให้กับอินสแตนซ์เท่านั้น สิ่งนี้อาจเปลี่ยนแปลงได้เมื่อคุณใช้ตัวดำเนินการที่เท่าเทียมกันมากเกินไป


15
@PatrickHofman ดูเหมือนว่าisสายobject.Equals(x, null)ในขณะที่คอมไพล์เป็น== ceqแต่ผลลัพธ์ควรเหมือนกันตามที่คุณพูด
svick

16
ระวังไว้เสมอว่า==เป็นตัวดำเนินการที่มากเกินไป คุณสามารถมีพฤติกรรมใด ๆ ที่คุณต้องการด้วย สำหรับตัวอย่างเช่นการติดตั้งแบบแปลก ๆ==นี้จะไม่บอกคุณว่าอินสแตนซ์ของคุณเป็นโมฆะจริงหรือไม่ is nullในทางกลับกันจะคืนค่าจริงสำหรับการอ้างอิง null จริง :) นอกจากนี้หากคุณมีReferenceEqualsรหัสของคุณหลอดไฟ VS 2017 จะแนะนำให้เปลี่ยนเป็นis nullไม่ใช่== null(ถูกต้อง)
nawfal

2
@PatrickHofman @svick การตรวจสอบ null สองครั้งในตอนนี้คอมไพล์ในสิ่งเดียวกันดังนั้นจึงisไม่มีโอเวอร์เฮดของการเรียกฟังก์ชันเมื่อใช้เพื่อตรวจสอบ null สำหรับหลักฐานดูลิงค์ที่โพสต์โดย @svick ในความคิดเห็น
AndreasHassing

1
@ AndreasBjørnการติดตาม Nielsen อัปเดตคำตอบของฉัน
Patrick Hofman

2
@PatrickHofman ไม่ควร IL เป็นวิธีอื่น ๆ ? == เรียก System.Object :: Equals (object, object) และเป็นค่า null ที่เรียกว่า ceq
Zbigniew Ledwoń

68

โอเวอร์โหลดเท่ากับโอเปอเรเตอร์

ในความเป็นจริงมีความแตกต่างในความหมายระหว่างการเปรียบเทียบทั้งสองเมื่อคุณเปรียบเทียบnullกับประเภทที่มี==ผู้ให้บริการมากเกินไป foo is nullจะใช้การเปรียบเทียบการอ้างอิงโดยตรงเพื่อกำหนดผลลัพธ์ในขณะที่foo == nullแน่นอนว่าจะเรียกใช้ตัวดำเนินการโอเวอร์โหลด==หากมีอยู่

ในตัวอย่างนี้ฉันได้แนะนำ "บั๊ก" ในตัว==ดำเนินการโอเวอร์โหลดทำให้เกิดข้อผิดพลาดเสมอถ้าอาร์กิวเมนต์ที่สองคือnull:

void Main()
{
    Foo foo = null;

    if (foo is null) Console.WriteLine("foo is null"); // This condition is met
    if (foo == null) Console.WriteLine("foo == null"); // This will throw an exception
}

public class Foo
{
    public static bool operator ==(Foo foo1, Foo foo2)
    {
        if (object.Equals(foo2, null)) throw new Exception("oops");
        return object.Equals(foo1, foo2);
    }

    // ...
}

รหัส IL สำหรับfoo is nullใช้ceqคำสั่งเพื่อทำการเปรียบเทียบการอ้างอิงโดยตรง:

IL_0003:  ldloc.0     // foo
IL_0004:  ldnull      
IL_0005:  ceq

รหัส IL สำหรับfoo == nullใช้การโทรไปยังโอเปอเรเตอร์ที่โอเวอร์โหลด:

IL_0016:  ldloc.0     // foo
IL_0017:  ldnull      
IL_0018:  call        UserQuery+Foo.op_Equality

ดังนั้นความแตกต่างคือถ้าคุณใช้==คุณเสี่ยงต่อการใช้รหัสผู้ใช้ (ซึ่งอาจมีพฤติกรรมที่ไม่คาดคิดหรือปัญหาประสิทธิภาพ)

ข้อ จำกัด เกี่ยวกับยาชื่อสามัญ

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

bool IsNull<T>(T item) => item is null;                  // Compile error: CS0403
bool IsNull<T>(T item) => item == null;                  // Works
bool IsNull<T>(T item) where T : class => item is null;  // Works

ขอบคุณDavid Augusto Villa ที่ชี้ให้เห็น


2
นอกจากนี้ note (x เป็นโมฆะ) ต้องใช้ข้อ จำกัด ของคลาสหาก x เป็นประเภททั่วไปในขณะที่ (x == null) และ object.ReferenceEquals (x, null) ไม่ได้
David Augusto Villa
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.