문제

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

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)로 변경할 것을 제안했습니다.이 문제를 해결하는 더 좋은 방법이 있습니까?

지원해야 해요 둘 다 참조 유형 및 값 유형.

도움이 되었습니까?

해결책

박싱을 피하기 위해 제네릭의 동등성을 비교하는 가장 좋은 방법은 다음과 같습니다. EqualityComparer<T>.Default.이는 존중합니다 IEquatable<T> (복싱 제외) 뿐만 아니라 object.Equals, 모든 작업을 처리합니다. Nullable<T> "들어진"뉘앙스.따라서:

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

이는 다음과 일치합니다.

  • 클래스의 경우 null
  • null(비어 있음) Nullable<T>
  • 다른 구조체의 경우 0/false/etc

다른 팁

이건 어때:

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

사용하여 static object.Equals() 방법을 사용하면 다음 작업을 수행할 필요가 없습니다. null 자신을 확인하십시오.다음을 사용하여 호출을 명시적으로 한정합니다. object. 아마도 상황에 따라 필요하지 않을 수도 있지만 일반적으로 접두사를 사용합니다. static 코드의 가용성을 높이기 위해 유형 이름을 호출합니다.

나는 Microsoft Connect 기사 이 문제를 좀 더 자세히 설명합니다.

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

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

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

그런 다음 CompareTo 에 의해 정의된 방법 IComparable 인터페이스.

이 시도:

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

컴파일하고 원하는 작업을 수행해야 합니다.

(수정됨)

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

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

여기서 문제가 발생합니다 -

이것이 모든 유형에 대해 작동하도록 허용하려는 경우 default(T)는 참조 유형의 경우 항상 null이고 값 유형의 경우 0(또는 0으로 가득 찬 구조체)입니다.

하지만 이것은 아마도 당신이 추구하는 행동이 아닐 것입니다.이것이 일반적인 방식으로 작동하도록 하려면 리플렉션을 사용하여 T 유형을 확인하고 참조 유형과 다른 값 유형을 처리해야 할 수 있습니다.

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

아마도 이 논리를 두 부분으로 나누고 먼저 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일 수 없다는 사실에 의존하고 있으므로 value가 ValueType에서 파생된 클래스인 경우 이미 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) ...
}

나는 사용한다:

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

이것이 귀하의 요구 사항에 맞는지 여부는 알 수 없지만 T를 IComparable과 같은 인터페이스를 구현하는 유형으로 제한한 다음 다음과 같이 해당 인터페이스(IIRC가 null을 지원/처리함)에서 ComparesTo() 메서드를 사용할 수 있습니다. :

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

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

@iliritit:

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

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

위에서 제안한 대로 Equals 메서드나 object.Equals를 호출한 후 명시적인 null 테스트 없이는 이 작업을 수행할 수 있는 방법이 생각나지 않습니다.

System.Comparison을 사용하여 솔루션을 고안할 수 있지만 실제로는 훨씬 더 많은 코드 줄이 필요하고 복잡성이 상당히 증가하게 됩니다.

내 생각엔 당신이 가까웠던 것 같아요.

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

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

우아한 Null 안전 접근 방식을 위해서는 같음이 호출되는 개체를 반전시키기만 하면 됩니다.

default(T).Equals(myArgument);
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top