무한 재귀없이 '=='연산자 오버로드에서 null을 어떻게 확인합니까?


113

다음은 == 연산자 오버로드 메서드에서 무한 재귀를 발생시킵니다.

    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);
    }

널은 어떻게 확인합니까?

답변:


138

사용 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
미래의 방문자에게 허용되는 대답은 == of object를 실행하는 함수입니다. 이것은 기본적으로 받아 들여진 대답과 동일하지만 한 가지 단점이 있습니다. 캐스트가 필요합니다. 따라서 승인 된 답변이 우수합니다.
— Mafii

1
@Mafii 캐스트는 순전히 컴파일 시간 작업입니다. 컴파일러는 캐스트가 실패 할 수 없음을 알고 있으므로 런타임에 아무것도 확인할 필요가 없습니다. 방법의 차이점은 완전히 미학적입니다.
— Servy

8

사용 ReferenceEquals. 로부터 MSDN 포럼 :

public static bool operator ==(Foo foo1, Foo foo2) {
    if (ReferenceEquals(foo1, null)) return ReferenceEquals(foo2, null);
    if (ReferenceEquals(foo2, null)) return false;
    return foo1.field1 == foo2.field2;
}

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);
}

운영자 ==는 나를 위해 모든 null 검사를 수행 한 후 foo1.Equals(foo2)두 가지가 동일한 경우 실제 검사를 수행하도록 재정의했다고 호출 합니다.


이것은 매우 적절하다고 느낍니다. 의 구현을 Object.Equals(Object, Object)나란히 살펴보면 다른 답변에서 제안한대로 모든 것을 즉시 수행하는 것이 Object.ReferenceEquals(Object, Object)매우 분명합니다 Object.Equals(Object, Object). 왜 사용하지 않습니까?
— tne

@tne ==원하는 모든 것이 기본 동작이면 연산자를 오버로딩하는 지점이 없기 때문 입니다. 사용자 지정 비교 논리를 구현해야하는 경우에만 오버로드해야합니다. 즉, 참조 동등성 검사 이상입니다.
— Dan Bechard 2015

@Dan 나는 당신이 내 발언을 오해했다고 확신합니다. 오버로딩 ==이 바람직하다는 것이 이미 확립 된 맥락에서 (질문이 암시 함) 나는 단순히 Object.Equals(Object, Object)사용 ReferenceEquals또는 명시 적 캐스트 와 같은 다른 트릭을 불필요하게 만드는 것을 제안함으로써이 답변을 지원하고 있습니다 (따라서 "왜 사용하지 않습니까?", "그것"). 인 Equals(Object, Object)). 관련이없는 경우에도 귀하의 요점이 정확하고 더 나아갈 것입니다. ==객체에 대한 과부하 만 "가치 객체"로 분류 할 수 있습니다.
— tne

@tne 주요 차이점은 Foo가 재정의 할 가능성이있는 가상 메서드 인 Object.Equals (Object) 를 Object.Equals(Object, Object)차례로 호출 한다는 것입니다. 동등성 검사에 가상 호출을 도입했다는 사실은 이러한 호출을 최적화 (예 : 인라인)하는 컴파일러의 기능에 영향을 미칠 수 있습니다. 이것은 대부분의 목적에서 무시할 수있을 것입니다. 그러나 어떤 경우에는 같음 연산자의 작은 비용이 루프 나 정렬 된 데이터 구조에 막대한 비용을 의미 할 수 있습니다.
— Dan Bechard 2015

@tne 가상 메서드 호출 최적화의 복잡성에 대한 자세한 내용은 stackoverflow.com/questions/530799/…를 참조하십시오 .
— Dan Bechard 2015

3

C # 7 이상을 사용하는 경우 null 상수 패턴 일치를 사용할 수 있습니다.

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

이것은 하나의 호출 객체보다 약간 깔끔한 코드를 제공합니다.


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 : 독자는 비교 DbNull, IMO 와 같은 기능을 사용하기 전에 종속성을 확인해야합니다 . 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 검사가 단순화됩니다.

이것은 ==,! =, Equals 및 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;
}

Equals () 및 연산자 오버로드에 대한 참조 지침 ==


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를 사용하도록 현재 프로젝트를 즉시 수정했습니다. 그러나 차선책 임에도 불구하고 작동하므로 질문에 대한 유효한 대답입니다.
— The Digital Gabeg
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.