무한 재귀없이 '=='연산자 오버로드에서 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)메소드는 두 개체 여부를 나타내는, objAobjB동일하다. 또한 값이 같은 개체를 테스트 할 수도 있습니다 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.