다음과 같이 정의 된 일반 메소드가 있습니다.
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;
}
다음과 일치합니다.
Nullable<T>
에 대해 null (비어 있음)이건 어때요:
if (object.Equals(myArgument, default(T)))
{
//...
}
static object.Equals()
메소드를 사용하면 null
점검을 직접 수행 할 필요가 없습니다. 문맥에 따라 object.
를 사용하여 명시 적으로 호출 할 필요는 없지만 코드의 가용성을 높이기 위해 static
호출 앞에 유형 이름을 붙입니다.
이 문제에 대해 자세히 설명하는 Microsoft Connect article 를 찾을 수있었습니다.
불행히도이 동작은 의도적으로 설계된 동작이며 값 형식을 포함 할 수있는 형식 매개 변수와 함께 사용할 수있는 쉬운 솔루션은 없습니다.
유형이 참조 유형으로 알려진 경우, 객체에 정의 된 기본 과부하는 변수가 참조 동등성을 테스트하지만, 유형에 고유 한 사용자 정의 과부하가 지정 될 수 있습니다. 컴파일러는 변수의 정적 유형에 따라 사용할 과부하를 결정합니다 (결정은 다형성이 아님). 따라서 제네릭 형식 매개 변수 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'유형의 피연산자에 'operator'== '을 (를) 적용 할 수 없습니다.
또 다른 옵션은 T를 IComparable
를 구현하는 모든 유형으로 제한하는 것입니다.
public void MyMethod<T>(T myArgument) where T : IComparable
그런 다음 IComparable interface 로 정의 된 CompareTo
메소드를 사용하십시오.
이 시도:
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 년에 갇혀 있으며 그것이 허용되는지 궁금합니다.
Edit : 확장 방법으로 작동시키는 방법은 다음과 같습니다.
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 (또는 struct full of 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 일 수 없다는 사실에 의존하고 있으므로 valueType에서 파생되는 클래스가 value이면 null이 아니라는 것을 이미 알고 있습니다. 반면에 값 유형이 아닌 경우 객체에 캐스트 된 값을 null과 비교할 수 있습니다. 객체로 캐스트로 직접 이동하여 ValueType에 대한 검사를 피할 수 있지만 이는 값 유형이 상자로 표시되어 새 객체가 힙에 작성됨을 의미하므로 피하고 싶을 것입니다.
IsNullOrEmpty 메서드에서 문자열의 특수한 경우를 확인하고 있습니다. 다른 모든 유형의 경우 모든 참조 유형의 경우 null이고 값 유형의 경우 일반적으로 0 형식입니다 (이미 알고있는 경우 not null 임). 필수입니다.
이러한 방법을 사용하면 다음 코드가 예상대로 작동합니다.
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;
}
}