ฉันจะตรวจสอบ null ในโอเวอร์โหลดตัวดำเนินการ '==' โดยไม่มีการเรียกซ้ำแบบไม่สิ้นสุดได้อย่างไร


114

สิ่งต่อไปนี้จะทำให้เกิดการเรียกซ้ำแบบไม่สิ้นสุดในวิธีการโอเวอร์โหลดตัวดำเนินการ ==

    Foo foo1 = null;
    Foo foo2 = new Foo();
    Assert.IsFalse(foo1 == foo2);

    public static bool operator ==(Foo foo1, Foo foo2) {
        if (foo1 == null) return foo2 == null;
        return foo1.Equals(foo2);
    }

ฉันจะตรวจสอบค่าว่างได้อย่างไร

คำตอบ:


139

ใช้ReferenceEquals:

Foo foo1 = null;
Foo foo2 = new Foo();
Assert.IsFalse(foo1 == foo2);

public static bool operator ==(Foo foo1, Foo foo2) {
    if (object.ReferenceEquals(null, foo1))
        return object.ReferenceEquals(null, foo2);
    return foo1.Equals(foo2);
}

วิธีนี้ใช้ไม่ได้สำหรับAssert.IsFalse(foo2 == foo1);
FIL

และfoo1.Equals(foo2)หมายความว่าอย่างไรตัวอย่างเช่นฉันต้องการfoo1 == foo2เฉพาะถ้าfoo1.x == foo2.x && foo1.y == foo2.y? คำตอบนี้ไม่ได้เพิกเฉยต่อกรณีที่foo1 != nullแต่foo2 == null?
Daniel

หมายเหตุ: วิธีแก้ปัญหาเดียวกันกับไวยากรณ์ที่ง่ายกว่า:if (foo1 is null) return foo2 is null;
Rem

20

ส่งไปยังวัตถุในวิธีโอเวอร์โหลด:

public static bool operator ==(Foo foo1, Foo foo2) {
    if ((object) foo1 == null) return (object) foo2 == null;
    return foo1.Equals(foo2);
}

1
ตรง ทั้งสอง(object)foo1 == nullหรือfoo1 == (object)nullจะไปในตัวเกินและไม่เกินที่ผู้ใช้กำหนด==(object, object) ==(Foo, Foo)มันเหมือนกับการแก้ปัญหาเกินพิกัดในวิธีการ
Jeppe Stig Nielsen

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

1
@Mafii นักแสดงเป็นเพียงการดำเนินการรวบรวมเวลา เนื่องจากคอมไพเลอร์รู้ว่าการแคสต์ไม่สามารถล้มเหลวได้จึงไม่จำเป็นต้องตรวจสอบอะไรเลยในรันไทม์ ความแตกต่างระหว่างวิธีการนั้นมีความสวยงามอย่างสมบูรณ์
Servy


4

ลอง Object.ReferenceEquals(foo1, null)

อย่างไรก็ตามฉันไม่แนะนำให้ใช้ตัว==ดำเนินการมากเกินไป ควรใช้เพื่อเปรียบเทียบการอ้างอิงและใช้Equalsสำหรับการเปรียบเทียบแบบ "ความหมาย"


4

หากฉันได้ลบล้างbool Equals(object obj)และฉันต้องการตัวดำเนินการ==และFoo.Equals(object obj)ส่งคืนค่าเดียวกันฉันมักจะใช้ตัว!=ดำเนินการดังนี้:

public static bool operator ==(Foo foo1, Foo foo2) {
  return object.Equals(foo1, foo2);
}
public static bool operator !=(Foo foo1, Foo foo2) {
  return !object.Equals(foo1, foo2);
}

==หลังจากนั้นโอเปอเรเตอร์จะทำการตรวจสอบค่าว่างทั้งหมดให้ฉันจบลงด้วยการเรียกfoo1.Equals(foo2)ว่าฉันได้ลบล้างเพื่อทำการตรวจสอบจริงว่าทั้งสองเท่ากันหรือไม่


สิ่งนี้เหมาะสมมาก เมื่อมองไปที่การใช้งานObject.Equals(Object, Object)ควบคู่กันไปObject.ReferenceEquals(Object, Object)ก็ค่อนข้างชัดเจนว่าObject.Equals(Object, Object)ทำทุกอย่างตามที่แนะนำในคำตอบอื่น ๆ นอกกรอบ ทำไมไม่ใช้ล่ะ?
tne

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

@ แดนฉันมั่นใจว่าคุณเข้าใจผิดคำพูดของฉัน ในบริบทที่เป็นที่ยอมรับแล้วว่าการใช้งานเกิน==กำลังเป็นที่พึงปรารถนา (คำถามบอกเป็นนัย ๆ ) ฉันเพียงแค่สนับสนุนคำตอบนี้โดยการแนะนำให้Object.Equals(Object, Object)ใช้เทคนิคอื่น ๆ เช่นการใช้ReferenceEqualsหรือการร่ายอย่างชัดเจนโดยไม่จำเป็น (ดังนั้น "ทำไมไม่ใช้มัน", "มัน" เป็นEquals(Object, Object)). แม้ว่าจุดที่ไม่เกี่ยวข้องของคุณก็ถูกต้องเช่นกันและฉันจะไปไกลกว่านั้น: เฉพาะการโอเวอร์โหลด==สำหรับวัตถุที่เราสามารถจัดประเภทเป็น "ออบเจ็กต์มูลค่า" ได้
tne

@tne ความแตกต่างที่สำคัญคือObject.Equals(Object, Object)ในทางกลับกันเรียกObject.Equals (Object)ซึ่งเป็นวิธีการเสมือนจริงที่ Foo น่าจะแทนที่ ความจริงที่ว่าคุณได้แนะนำการโทรเสมือนในการตรวจสอบความเท่าเทียมกันของคุณอาจส่งผลต่อความสามารถของคอมไพเลอร์ในการเพิ่มประสิทธิภาพ (เช่นอินไลน์) การเรียกเหล่านี้ สิ่งนี้อาจไม่สำคัญสำหรับวัตถุประสงค์ส่วนใหญ่ แต่ในบางกรณีค่าใช้จ่ายเพียงเล็กน้อยในตัวดำเนินการความเท่าเทียมกันอาจหมายถึงต้นทุนมหาศาลสำหรับการวนซ้ำหรือโครงสร้างข้อมูลที่เรียงลำดับ
Dan Bechard

@tne สำหรับข้อมูลเพิ่มเติมเกี่ยวกับความซับซ้อนของการเพิ่มประสิทธิภาพการโทรวิธีเสมือนให้ดูstackoverflow.com/questions/530799/...
Dan Bechard

3

หากคุณใช้ C # 7 หรือใหม่กว่าคุณสามารถใช้การจับคู่รูปแบบคงที่ว่างได้:

public static bool operator==(Foo foo1, Foo foo2)
{
    if (foo1 is null)
        return foo2 is null;
    return foo1.Equals(foo2);
}

สิ่งนี้ให้รหัสที่ใกล้เคียงกับวัตถุที่เรียกใช้เล็กน้อย ReferenceEquals (foo1, null)


2
หรือpublic static bool operator==( Foo foo1, Foo foo2 ) => foo1?.Equals( foo2 ) ?? foo2 is null;
Danko Durbić

3

มีวิธีตรวจสอบที่ง่ายกว่าnullในกรณีนี้:

if (foo is null)

แค่นั้นแหละ!

คุณลักษณะนี้ได้รับการแนะนำใน C # 7


1

แนวทางของฉันคือการทำ

(object)item == null

ซึ่งฉันอาศัยobjectตัวดำเนินการความเท่าเทียมกันของตัวเองซึ่งไม่สามารถผิดพลาดได้ หรือวิธีการขยายที่กำหนดเอง (และการโอเวอร์โหลด):

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null;
}

public static bool IsNull<T>(this T? obj) where T : struct
{
    return !obj.HasValue;
}

หรือเพื่อจัดการกรณีอื่น ๆ อาจเป็น:

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null || obj == DBNull.Value;
}

ข้อ จำกัด ป้องกันIsNullประเภทค่า ตอนนี้มันหวานเหมือนการโทร

object obj = new object();
Guid? guid = null; 
bool b = obj.IsNull(); // false
b = guid.IsNull(); // true
2.IsNull(); // error

ซึ่งหมายความว่าฉันมีรูปแบบการตรวจสอบค่า null ที่สอดคล้อง / ไม่ผิดพลาดตลอด ฉันพบว่า(object)item == nullเร็วกว่าเล็กน้อยมากObject.ReferenceEquals(item, null)แต่เฉพาะในกรณีที่มีความสำคัญ (ฉันกำลังดำเนินการบางอย่างที่ฉันจะเพิ่มประสิทธิภาพทุกอย่างในระดับไมโคร!)

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


Nitpick: ผู้อ่านควรดูการอ้างอิงของพวกเขาก่อนที่จะกระโดดในคุณสมบัติเช่นการเปรียบเทียบDbNullIMO กรณีที่สิ่งนี้ไม่ก่อให้เกิดปัญหาที่เกี่ยวข้องกับSRPนั้นค่อนข้างหายาก เพียงแค่ชี้ให้เห็นกลิ่นของรหัสมันก็น่าจะเหมาะสมมาก
tne

0

Equals(Object, Object)วิธีการคงที่ระบุว่าวัตถุสองชิ้นobjAและobjBมีค่าเท่ากันหรือไม่ นอกจากนี้ยังช่วยให้คุณทดสอบวัตถุที่มีค่าnullเพื่อความเท่าเทียมกัน เปรียบเทียบobjAและobjBเพื่อความเท่าเทียมกันดังนี้:

  • กำหนดว่าวัตถุทั้งสองเป็นตัวแทนของการอ้างอิงวัตถุเดียวกันหรือไม่ หากเป็นเช่นนั้นวิธีการจะกลับtrueมา การทดสอบนี้เทียบเท่ากับการเรียกใช้ReferenceEqualsเมธอด นอกจากนี้ถ้าทั้งสองobjAและobjBเป็นnullวิธีการจะคืนค่าtrueวิธีการส่งกลับ
  • จะเป็นตัวกำหนดว่าอย่างใดอย่างหนึ่งobjAหรือเป็นobjB nullถ้าเป็นเช่นนั้นมันจะกลับfalseมา หากวัตถุทั้งสองไม่ได้แสดงถึงการอ้างอิงอ็อบเจ็กต์เดียวกันและไม่เป็นnullเหมือนกันมันจะเรียกobjA.Equals(objB)และส่งคืนผลลัพธ์ ซึ่งหมายความว่าถ้าobjAแทนที่Object.Equals(Object)เมธอดการลบล้างนี้จะถูกเรียกว่า

.

public static bool operator ==(Foo objA, Foo objB) {
    return Object.Equals(objA, objB);
}

0

การตอบกลับเพิ่มเติมเกี่ยวกับการแทนที่โอเปอเรเตอร์วิธีเปรียบเทียบกับ nullที่เปลี่ยนเส้นทางที่นี่ว่าซ้ำกัน

ในกรณีที่ทำสิ่งนี้เพื่อรองรับ Value Objects ฉันพบว่าสัญกรณ์ใหม่มีประโยชน์และต้องการให้แน่ใจว่ามีเพียงที่เดียวที่ทำการเปรียบเทียบ นอกจากนี้ยังใช้ประโยชน์จาก Object.Equals (A, B) ทำให้การตรวจสอบ null ง่ายขึ้น

สิ่งนี้จะเกิน ==,! =, เท่ากับและ GetHashCode

    public static bool operator !=(ValueObject self, ValueObject other) => !Equals(self, other);
    public static bool operator ==(ValueObject self, ValueObject other) => Equals(self, other);
    public override bool Equals(object other) => Equals(other as ValueObject );
    public bool Equals(ValueObject other) {
        return !(other is null) && 
               // Value comparisons
               _value == other._value;
    }
    public override int GetHashCode() => _value.GetHashCode();

สำหรับวัตถุที่ซับซ้อนมากขึ้นให้เพิ่มการเปรียบเทียบเพิ่มเติมใน Equals และ GetHashCode ที่สมบูรณ์ยิ่งขึ้น


0

สำหรับไวยากรณ์ที่ทันสมัยและย่อ:

public static bool operator ==(Foo x, Foo y)
{
    return x is null ? y is null : x.Equals(y);
}

public static bool operator !=(Foo x, Foo y)
{
    return x is null ? !(y is null) : !x.Equals(y);
}

-3

ข้อผิดพลาดที่พบบ่อยในทับถมของผู้ประกอบการ == คือการใช้(a == b), (a ==null)หรือ(b == null)การตรวจสอบเพื่อความเท่าเทียมกันการอ้างอิง สิ่งนี้ ส่งผลให้เกิดการเรียกใช้ตัวดำเนินการมากเกินไป == ทำให้เกิดinfinite loopไฟล์. ใช้ReferenceEqualsหรือส่งประเภทเป็น Object เพื่อหลีกเลี่ยงการวนซ้ำ

ตรวจสอบสิ่งนี้

// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))// using ReferenceEquals
{
    return true;
}

// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))// using casting the type to Object
{
    return false;
}

อ้างอิง แนวทางสำหรับการโอเวอร์โหลดเท่ากับ () และตัวดำเนินการ ==


1
มีคำตอบหลายคำตอบพร้อมข้อมูลทั้งหมดนี้ เราไม่ต้องการสำเนาที่ 7 ของคำตอบเดียวกัน
Servy

-5

คุณสามารถลองใช้คุณสมบัติอ็อบเจ็กต์และจับผลลัพธ์ NullReferenceException หากคุณสมบัติที่คุณพยายามสืบทอดหรือแทนที่จาก Object สิ่งนี้ใช้ได้กับคลาสใด ๆ

public static bool operator ==(Foo foo1, Foo foo2)
{
    //  check if the left parameter is null
    bool LeftNull = false;
    try { Type temp = a_left.GetType(); }
    catch { LeftNull = true; }

    //  check if the right parameter is null
    bool RightNull = false;
    try { Type temp = a_right.GetType(); }
    catch { RightNull = true; }

    //  null checking results
    if (LeftNull && RightNull) return true;
    else if (LeftNull || RightNull) return false;
    else return foo1.field1 == foo2.field2;
}

หากคุณมีวัตถุว่างจำนวนมากการจัดการข้อยกเว้นอาจมีค่าใช้จ่ายสูง
Kasprzol

2
ฮ่า ๆ ฉันยอมรับว่านี่ไม่ใช่วิธีที่ดีที่สุด หลังจากโพสต์วิธีนี้ฉันได้แก้ไขโครงการปัจจุบันของฉันทันทีเพื่อใช้ ReferenceEquals แทน อย่างไรก็ตามแม้จะไม่เหมาะสม แต่ก็ใช้ได้ผลและเป็นคำตอบที่ถูกต้องสำหรับคำถามนี้
Digital Gabeg
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.