C #에서 일반 인수의 널 또는 기본 비교


288

다음과 같이 정의 된 일반 메소드가 있습니다.

public void MyMethod<T>(T myArgument)

가장 먼저 할 일은 myArgument의 값이 해당 유형의 기본값인지 확인하는 것입니다.

if (myArgument == default(T))

그러나 T가 == 연산자를 구현한다고 보장하지 않기 때문에 컴파일되지 않습니다. 그래서 코드를 다음과 같이 전환했습니다.

if (myArgument.Equals(default(T)))

이제 이것은 컴파일되지만 myArgument가 null 인 경우 실패합니다. 이것은 내가 테스트하는 것의 일부입니다. 다음과 같이 명시 적 null 검사를 추가 할 수 있습니다.

if (myArgument == null || myArgument.Equals(default(T)))

이제 이것은 나에게 중복 느낌입니다. ReSharper는 myArgument == null 부분을 시작한 곳인 myArgument == default (T)로 변경한다고 제안합니다. 이 문제를 해결하는 더 좋은 방법이 있습니까?

참조 유형과 값 유형을 모두 지원해야합니다 .


C #은 이제 Null Conditional Operators를 지원 합니다.이 연산자 는 마지막 예제의 구문 설탕입니다. 코드가됩니다 if (myArgument?.Equals( default(T) ) != null ).
wizard07KSU

1
@ wizard07KSU 값 유형에 대해서는 작동하지 않습니다. 즉, 이 경우에는 불가능 하고 (부울) 의 결과 는 절대로되지 true않기 때문에 Equals항상 값 유형을 호출해야 하기 때문에 평가됩니다 . myArgumentnullEqualsnull
재스퍼

똑같이 귀중한 거의 중복 (투표하지 않음) : operator == C #의 일반 유형에 적용 할 수 없습니까?
GSerg

답변:


585

복싱을 피하기 위해 제네릭을 동등하게 비교하는 가장 좋은 방법은입니다 EqualityComparer<T>.Default. 이것은 IEquatable<T>(권투없이)뿐만 아니라 object.Equals모든 Nullable<T>"리프트 된"뉘앙스를 처리합니다 . 그 후:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}

이것은 다음과 일치합니다.

  • 클래스의 경우는 null
  • 에 대해 null (빈) Nullable<T>
  • 다른 구조체의 경우 0 / 거짓 / 등

29
와우, 얼마나 유쾌하게 모호한가! 이것은 확실히 갈 길입니다, kudos.
Nick Farina

1
정답입니다. 이 솔루션을 사용하기 위해 다시 작성한 후 코드에 구불 구불 한 줄이 없습니다.
Nathan Ridley

14
좋은 답변입니다! obj.IsDefaultForType ()
rikoe

2
의 경우 @nawfal Person, p1.Equals(p2)그 여부가 구현에 따라 달라집니다 IEquatable<Person>공개 API에, 또는 명시 적 구현을 통해 - 즉, 컴파일러는 공중 볼 수있는 Equals(Person other)방법을. 하나; 에서 제너릭 같은 IL 모두에 사용된다 T; T1구현하는 일이 그 IEquatable<T1>요구는 동일하게 취급하기 T2때문에 아니,이 -하지 않습니다 자리 Equals(T1 other)가 런타임에 존재하는 경우에도, 방법. 두 경우 모두에 null대해 생각해야합니다 (둘 중 하나). 따라서 제네릭을 사용하면 게시 한 코드를 사용합니다.
Marc Gravell

5
이 답변이 나를 정신 이상에서 멀게하거나 더 가깝게 밀 었는지 결정할 수 없습니다. +1
Steven Liekens

118

이건 어때요:

if (object.Equals(myArgument, default(T)))
{
    //...
}

static object.Equals()방법을 사용하면 null직접 점검 할 필요가 없습니다 . object.컨텍스트에 따라 호출을 명시 적으로 한정 할 필요는 없지만 static코드의 가용성을 높이기 위해 호출에 유형 이름이 붙는 접두사 를 사용합니다.


2
"객체"를 떨어 뜨릴 수도 있습니다. 중복되기 때문에 부분. if (Equals (myArgument, default (T)))
Stefan Moser 2018 년

13
사실, 그것은 일반적이지만 상황에 따라 다를 수 있습니다. 두 개의 인수를 취하는 인스턴스 Equals () 메소드가있을 수 있습니다. 코드를 읽기 쉽도록 모든 정적 호출에 클래스 이름을 명시 적으로 접는 경향이 있습니다.
Kent Boogaart

8
그것이 권투를 유발하고 어떤 경우에는 중요 할 수 있음을
명심

2
나를 위해 이미 상자에있는 정수를 사용할 때 작동하지 않습니다. 개체가되고 개체의 기본값이 0 대신에 null이기 때문입니다.
riezebosch

28

이 문제에 대해 자세히 설명 하는 Microsoft Connect 기사 를 찾을 수있었습니다 .

불행히도이 동작은 의도적으로 설계된 동작이며 값 형식을 포함 할 수있는 형식 매개 변수와 함께 사용할 수있는 쉬운 솔루션은 없습니다.

유형이 참조 유형으로 알려진 경우, 객체에 정의 된 기본 과부하는 변수가 참조 동등성을 테스트하지만 유형에 따라 자체 사용자 정의 과부하가 지정 될 수 있습니다. 컴파일러는 변수의 정적 유형에 따라 사용할 과부하를 결정합니다 (결정은 다형성이 아님). 따라서 제네릭 형식 매개 변수 T를 봉인되지 않은 참조 형식 (예 : 예외)으로 제한하도록 예제를 변경하면 컴파일러에서 사용할 특정 오버로드를 결정할 수 있으며 다음 코드는 컴파일됩니다.

public class Test<T> where T : Exception

유형이 값 유형으로 알려진 경우 사용 된 정확한 유형을 기반으로 특정 값 평등 테스트를 수행합니다. 참조 유형 비교는 값 유형에 의미가 없으며 컴파일러가 어떤 특정 값 비교를 수행 할 수 있는지 알 수 없기 때문에 여기서 "기본"비교는 적절하지 않습니다. 컴파일러는 ValueType.Equals (Object)를 호출 할 수 있지만이 방법은 리플렉션을 사용하며 특정 값 비교에 비해 상당히 비효율적입니다. 따라서 T에 값 유형 제약 조건을 지정하더라도 컴파일러가 여기에서 생성하는 것은 합리적이지 않습니다.

public class Test<T> where T : struct

제시 한 경우, 컴파일러가 T가 값인지 참조 유형인지조차 알지 못하는 경우, 가능한 모든 유형에 유효한 것을 생성 할 것은 없습니다. 참조 비교는 값 유형에 유효하지 않으며 일부 종류의 값 비교는 과부하되지 않은 참조 유형에 대해서는 예상치 못한 것입니다.

여기 당신이 할 수있는 일이 있습니다 ...

이 두 가지 방법이 참조 및 값 유형의 일반적인 비교에 효과적이라는 것을 확인했습니다.

object.Equals(param, default(T))

또는

EqualityComparer<T>.Default.Equals(param, default(T))

"=="연산자와 비교하려면 다음 방법 중 하나를 사용해야합니다.

T의 모든 사례가 알려진 기본 클래스에서 파생 된 경우 일반 유형 제한을 사용하여 컴파일러에 알릴 수 있습니다.

public void MyMethod<T>(T myArgument) where T : MyBase

그런 다음 컴파일러는 작업을 수행하는 방법을 인식 MyBase하고 현재보고있는 'T'및 'T'유형의 피연산자에 '운영자'== '를 적용 할 수 없습니다.

또 다른 옵션은 T를 구현하는 모든 유형으로 제한하는 것 IComparable입니다.

public void MyMethod<T>(T myArgument) where T : IComparable

그런 다음 IComparable 인터페이스에서CompareTo 정의한 메소드 를 사용하십시오 .


4
"이 동작은 의도적으로 설계된 것이며 값 형식을 포함 할 수있는 형식 매개 변수와 함께 사용할 수있는 쉬운 솔루션은 없습니다." 실제로 Microsoft는 잘못되었습니다. 쉬운 해결책이 있습니다. MS는 ceq opcode를 확장하여 비트 연산자로 값 유형에서 작동하도록해야합니다. 그런 다음이 opcode를 사용하는 내장 함수를 제공 할 수 있습니다 (예 : ceq를 사용하는 object.BitwiseOrReferenceEquals <T> (value, default (T))). 값 및 참조 유형 모두 값의 비트 별 동등성 확인 합니다 (하지만 참조 유형의 경우 참조 비트 단위 동일성은 object.ReferenceEquals와 동일)
Qwertie


18

이 시도:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))

컴파일하고 원하는 것을해야합니다.


<code> default (T) </ code>가 중복되지 않습니까? <code> EqualityComparer <T> .Default.Equals (myArgument) </ code>가 트릭을 수행해야합니다.
Joshcodes

2
1) 시도해 보았으며 2) 비교기 객체와 무엇을 비교하고 있습니까? 이 Equals방법 IEqualityComparer은 두 개의 인수, 두 개의 객체를 비교할 수 있으므로 중복되지 않습니다.
Lasse V. Karlsen 2016 년

이 답변은 권투 / 언 박싱 및 기타 유형을 처리하기 때문에 허용되는 답변 IMHO보다 훨씬 좋습니다. :이 질문에 '대답은 "잘 속는로 폐쇄"를 참조 stackoverflow.com/a/864860/210780
ashes999

7

(편집)

Marc Gravell이 가장 좋은 답변을 얻었지만 간단한 코드 스 니펫을 게시하고 싶었습니다. 간단한 C # 콘솔 앱에서 이것을 실행하십시오.

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null)); //True //True

    Console.ReadKey();
}

한 가지 더 : VS2008 사용자는 이것을 확장 방법으로 시도 할 수 있습니까? 나는 2005 년에 갇혀 있으며 그것이 허용되는지 궁금합니다.


편집 : 확장 방법으로 작동시키는 방법은 다음과 같습니다.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // value type
        Console.WriteLine(1.IsDefault());
        Console.WriteLine(0.IsDefault());

        // reference type
        Console.WriteLine("test".IsDefault());
        // null must be cast to a type
        Console.WriteLine(((String)null).IsDefault());
    }
}

// The type cannot be generic
public static class TypeHelper
{
    // I made the method generic instead
    public static bool IsDefault<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}

3
확장 방법으로 "작동"합니다. o가 null 일 때 o.IsDefault <object> ()라고해도 작동하기 때문에 흥미 롭습니다. Scary =)
Nick Farina 2016 년

6

여기서 T가 기본 유형 인 경우를 포함하여 모든 유형의 T를 처리하려면 두 가지 비교 방법으로 컴파일해야합니다.

    T Get<T>(Func<T> createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }

1
함수가 Func <T>를 수락하고 T를 반환하도록 변경되었습니다.
Nick Farina

ReSharper가 나를 어지럽히는 것 같습니다. 값 유형과 null 간의 가능한 비교에 대한 경고를 인식하지 못했지만 컴파일러 경고가 아닙니다.
Nathan Ridley

2
참고 : T가 값 유형 인 것으로 판명되면 null에 대한 비교는 지터에 의해 항상 거짓으로 처리됩니다.
Eric Lippert

의미가 있습니다-런타임은 포인터를 값 유형과 비교합니다. 그러나 Equals () 검사는이 경우 작동합니다 (흥미롭게도 컴파일하는 5.Equals (4)라고 말하는 매우 역동적 인 언어이기 때문에).
Nick Farina

2
권투 등을 포함하지 않는 대안에 대해서는 EqualityComparer <T> 답변을 참조하십시오
Marc Gravell

2

여기에 문제가 생길 것입니다-

이것이 모든 유형에 대해 작동하게하려면 참조 유형의 경우 default (T)는 항상 null이고 값 유형의 경우 0 (또는 struct full of 0)입니다.

그러나 이것은 아마도 당신이 따르는 행동이 아닙니다. 이것이 일반적인 방식으로 작동하게하려면 리플렉션을 사용하여 T 유형을 확인하고 참조 유형과 다른 값 유형을 처리해야합니다.

또는 인터페이스 제약 조건을 적용하고 인터페이스가 클래스 / 구조의 기본값을 확인하는 방법을 제공 할 수 있습니다.


1

이 논리를 두 부분으로 나누고 null을 먼저 확인해야한다고 생각합니다.

public static bool IsNullOrEmpty<T>(T value)
{
    if (IsNull(value))
    {
        return true;
    }
    if (value is string)
    {
        return string.IsNullOrEmpty(value as string);
    }
    return value.Equals(default(T));
}

public static bool IsNull<T>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

IsNull 메서드에서는 ValueType 개체가 정의에 의해 null 일 수 없다는 사실에 의존하고 있으므로 valueType에서 파생되는 클래스가 value 인 경우 이미 null이 아니라는 것을 이미 알고 있습니다. 반면에 값 유형이 아닌 경우 객체에 캐스트 된 값을 null과 비교할 수 있습니다. 객체로 캐스트로 직접 이동하여 ValueType에 대한 검사를 피할 수 있지만 이는 값 유형이 상자로 표시되어 새 객체가 힙에 작성됨을 의미하므로 피하고 싶을 것입니다.

IsNullOrEmpty 메서드에서 문자열의 특수한 경우를 확인하고 있습니다. 다른 모든 유형의 경우, 값 (이미 null 이 아닌 것으로 알고 있음 )과 모든 참조 유형의 경우 널 (null) 인 값의 기본값과 값 유형의 값이 일반적으로 0 (일부 정수인 경우)의 형식과 비교합니다.

이러한 방법을 사용하면 다음 코드가 예상대로 작동합니다.

class Program
{
    public class MyClass
    {
        public string MyString { get; set; }
    }

    static void Main()
    {
        int  i1 = 1;    Test("i1", i1); // False
        int  i2 = 0;    Test("i2", i2); // True
        int? i3 = 2;    Test("i3", i3); // False
        int? i4 = null; Test("i4", i4); // True

        Console.WriteLine();

        string s1 = "hello";      Test("s1", s1); // False
        string s2 = null;         Test("s2", s2); // True
        string s3 = string.Empty; Test("s3", s3); // True
        string s4 = "";           Test("s4", s4); // True

        Console.WriteLine();

        MyClass mc1 = new MyClass(); Test("mc1", mc1); // False
        MyClass mc2 = null;          Test("mc2", mc2); // True
    }

    public static void Test<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}

1

허용 된 답변에 따른 확장 방법.

   public static bool IsDefault<T>(this T inObj)
   {
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

용법:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue == null || tValue.IsDefault()) return false;
   }

단순화하려면 null로 대체하십시오.

   public static bool IsNullOrDefault<T>(this T inObj)
   {
       if (inObj == null) return true;
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

용법:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue.IsNullOrDefault()) return false;
   }

0

나는 사용한다:

public class MyClass<T>
{
  private bool IsNull() 
  {
    var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
    return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
  }
}

-1

이것이 귀하의 요구 사항과 함께 작동하는지 여부를 모르지만 T를 IComparable과 같은 인터페이스를 구현하는 Type으로 제한 한 다음 해당 인터페이스 (IIRC가 널을 지원 / 처리)에서 ComparesTo () 메서드를 사용할 수 있습니다 :

public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))

IEquitable 등을 사용할 수있는 다른 인터페이스가있을 수 있습니다.


OP는 abt NullReferenceException이 걱정되어 동일하게 보장합니다.
nawfal

-2

@ilitirit :

public class Class<T> where T : IComparable
{
    public T Value { get; set; }
    public void MyMethod(T val)
    {
        if (Value == val)
            return;
    }
}

'T'및 'T'유형의 피연산자에는 '=='연산자를 적용 할 수 없습니다.

명시 적 null 테스트가 없으면 Equals 메서드 또는 객체를 호출하지 않고이를 수행하는 방법을 생각할 수 없습니다. 위에서 제안한 것과 같습니다.

System.Comparison을 사용하여 솔루션을 고안 할 수 있지만 실제로는 더 많은 코드 줄로 끝나고 복잡성이 크게 증가합니다.


-3

나는 당신이 가까이 있다고 생각합니다.

if (myArgument.Equals(default(T)))

이제 이것은 컴파일되지만 myArgumentnull 인 경우 실패합니다 . 이것은 테스트중인 것의 일부입니다. 다음과 같이 명시 적 null 검사를 추가 할 수 있습니다.

우아한 null 안전 접근을 위해 equals가 호출되는 객체를 뒤집기 만하면됩니다.

default(T).Equals(myArgument);

나는 똑같은 것을 생각하고 있었다.
Chris Gessler

6
참조 유형의 default (T)는 null이며 보장 된 NullReferenceException이 발생합니다.
Stefan Steinegger
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.