C #에서 연산자 ==를 제네릭 형식에 적용 할 수 없습니까?


326

MSDN== 운영자 의 문서에 따르면 ,

미리 정의 된 값 형식의 경우 항등 연산자 (==)는 피연산자의 값이 같으면 true를 반환하고 그렇지 않으면 false를 반환합니다. string 이외의 참조 유형의 경우 ==는 두 피연산자가 동일한 객체를 참조하면 true를 반환합니다. 문자열 유형의 경우 ==는 문자열 값을 비교합니다. 사용자 정의 값 유형은 == 연산자를 오버로드 할 수 있습니다 (연산자 참조). 기본적으로 ==는 미리 정의 된 참조 유형과 사용자 정의 참조 유형 모두에 대해 위에서 설명한대로 작동 하지만 사용자 정의 참조 유형도 가능합니다.

그렇다면 왜이 코드 스 니펫이 컴파일에 실패합니까?

bool Compare<T>(T x, T y) { return x == y; }

'T'및 'T'유형의 피연산자에 연산자 '=='를 적용 할 수 없다는 오류가 발생 합니다. ==연산자가 모든 유형에 대해 사전 정의되어 있다는 것을 이해하는 한 왜 궁금 합니다.

편집 : 감사합니다. 처음에는 그 진술이 참조 유형에 관한 것임을 알지 못했습니다. 나는 또한 그 비트 단위 비교가 지금 알고있는 모든 값 유형, 제공됩니다 생각 하지 올바른.

그러나 참조 유형을 사용하는 경우 ==연산자가 사전 정의 된 참조 비교를 사용합니까, 또는 유형이 정의 된 경우 오버로드 된 연산자 버전을 사용합니까?

편집 2 : 시행 착오를 통해 ==연산자가 무제한 제네릭 형식을 사용할 때 사전 정의 된 참조 비교를 사용 한다는 것을 알았습니다 . 실제로 컴파일러는 제한된 유형 인수에 대해 가장 좋은 방법을 사용하지만 더 이상 보지 않습니다. 예를 들어, 아래 코드 는 호출 된 true경우에도 항상 인쇄 됩니다 Test.test<B>(new B(), new B()).

class A { public static bool operator==(A x, A y) { return true; } }
class B : A { public static bool operator==(B x, B y) { return false; } }
class Test { void test<T>(T a, T b) where T : A { Console.WriteLine(a == b); } }

후속 질문에 대한 답변은 내 답변을 다시 참조하십시오.
Giovanni Galbo

제네릭이 없어도 ==동일한 유형의 두 피연산자 사이에 허용되지 않는 유형이 있음을 이해하는 것이 유용 할 수 있습니다 . 이는 struct과부하가 발생하지 않는 유형 ( "사전 정의 된"유형 제외)에 해당 operator ==됩니다. 간단한 예를 들어,이 시도 :var map = typeof(string).GetInterfaceMap(typeof(ICloneable)); Console.WriteLine(map == map); /* compile-time error */
Jeppe의 Stig 닐슨

내 자신의 오래된 의견을 계속합니다. 예를 들어 ( 다른 스레드 참조 )을 사용 하면 구조체 이기 때문에 var kvp1 = new KeyValuePair<int, int>(); var kvp2 = kvp1;확인할 수 없으며 C # 사전 정의 유형이 아니며을 오버로드하지 않습니다 . 그러나 당신이 할 수없는 예제가 있습니다 (여기서는 오버로드하지 않는 중첩 된 구조체 ( 런타임에 의해 호출 됨 )가 있습니다). kvp1 == kvp2KeyValuePair<,>operator ==var li = new List<int>(); var e1 = li.GetEnumerator(); var e2 = e1;e1 == e2List<>.Enumerator"List`1+Enumerator[T]"==
Jeppe Stig Nielsen

RE : "왜이 코드 스 니펫이 컴파일에 실패합니까?" - 어 ... 당신이를 반환 할 수 없기 때문에 boolA로부터 void...
BrainSlugs83

1
@ BrainSlugs83 10 살짜리 버그를 찾아 주셔서 감사합니다!
Hosam Aly

답변:


143

"... 기본적으로 ==는 사전 정의 된 참조 유형과 사용자 정의 된 참조 유형 모두에 대해 위에서 설명한대로 작동합니다."

타입 T는 반드시 레퍼런스 타입 일 필요는 없으므로 컴파일러는 그러한 가정을 할 수 없습니다.

그러나 더 명확하기 때문에 컴파일됩니다.

    bool Compare<T>(T x, T y) where T : class
    {
        return x == y;
    }

"하지만 참조 형식을 사용하는 경우 == 연산자가 미리 정의 된 참조 비교를 사용합니까 아니면 형식이 정의 된 경우 오버로드 된 연산자 버전을 사용합니까?"라는 추가 질문을 따릅니다.

Generics의 ==는 오버로드 된 버전을 사용한다고 생각했지만 다음 테스트에서는 그렇지 않습니다. 흥미 롭습니다 ... 이유를 알고 싶습니다! 누군가 알고 있다면 공유하십시오.

namespace TestProject
{
 class Program
 {
    static void Main(string[] args)
    {
        Test a = new Test();
        Test b = new Test();

        Console.WriteLine("Inline:");
        bool x = a == b;
        Console.WriteLine("Generic:");
        Compare<Test>(a, b);

    }


    static bool Compare<T>(T x, T y) where T : class
    {
        return x == y;
    }
 }

 class Test
 {
    public static bool operator ==(Test a, Test b)
    {
        Console.WriteLine("Overloaded == called");
        return a.Equals(b);
    }

    public static bool operator !=(Test a, Test b)
    {
        Console.WriteLine("Overloaded != called");
        return a.Equals(b);
    }
  }
}

산출

인라인 : 과부하 == 호출

일반적인:

계속하려면 아무 키나 누르십시오. . .

후속 2

비교 방법을 다음과 같이 바꾸는 것을 지적하고 싶습니다.

    static bool Compare<T>(T x, T y) where T : Test
    {
        return x == y;
    }

오버로드 된 == 연산자가 호출됩니다. 형식을 지정하지 않으면 ( where로 ) 컴파일러가 오버로드 된 연산자를 사용해야한다고 추론 할 수는 없지만 형식을 지정하지 않아도 결정을 내리기에 충분한 정보가 있다고 생각합니다.


감사. 진술 문이 참조 유형에 관한 것임을 알지 못했습니다.
Hosam Aly

4
Re : Follow Up 2 : 실제로 컴파일러는 찾은 최상의 방법 (이 경우 Test.op_Equal)을 링크합니다. 그러나 Test에서 파생되고 연산자를 재정의하는 클래스가 있으면 Test의 연산자가 계속 호출됩니다.
Hosam Aly

4
내가 지적하고 싶은 좋은 습관은 항상 연산자가 Equals아닌 재정의 된 메소드 내에서 실제 비교를 수행해야한다는 것 ==입니다.
jpbochi

11
과부하 해결은 컴파일 타임에 발생합니다. 따라서 ==제네릭 형식 T과와 사이에 있을 때 T어떤 제약 조건이 적용되는지를 고려할 때 최상의 과부하가 발견됩니다 T(이에 대한 값 유형을 상자에 넣지 않는 특별한 규칙이 있습니다 (무의미한 결과를 줄 것입니다). 참조 유형을 보장하는 일부 제약 조건). 당신에 후속이 당신이 올 경우, DerivedTest개체 및 DerivedTest도출에서 Test만 소개하고 새로운 과부하 ==, 당신은 다시 "문제"가됩니다. 어떤 오버로드가 호출되는지 컴파일 타임에 IL에 "굽습니다".
Jeppe Stig Nielsen

1
엄청나게 이것은 일반적인 참조 유형 (이 비교가 참조 평등에있을 것으로 예상되는 경우)에서 작동하는 것처럼 보이지만 문자열의 경우 참조 평등을 사용하는 것처럼 보입니다. 따라서 두 개의 동일한 문자열을 비교하고 == ( 클래스 제약 조건을 가진 일반 메소드)는 다르다고 말합니다.
JonnyRaa

291

다른 사람들이 말했듯이 T가 참조 유형으로 제한 된 경우에만 작동합니다. 제약 조건이 없으면 null과 비교할 수 있지만 null 만 가능하며 null이 아닌 값 형식의 경우 해당 비교는 항상 false입니다.

Equals를 호출하는 대신 IComparer<T>- 를 사용하는 것이 좋습니다. 더 이상 정보가 없으면 EqualityComparer<T>.Default좋은 선택입니다.

public bool Compare<T>(T x, T y)
{
    return EqualityComparer<T>.Default.Equals(x, y);
}

다른 것 외에도, 이것은 권투 / 캐스팅을 피합니다.


감사. 간단한 래퍼 클래스를 작성하려고했기 때문에 실제 래핑 된 멤버에게 작업을 위임하고 싶었습니다. 그러나 EqualityComparer <T>를 아는 것은 기본적으로 나에게 가치를 더했습니다. :)
Hosam Aly

옆으로, 존; 내 게시물에 의견 po povs yoda를 기록하고 싶을 수도 있습니다.
Marc Gravell

4
EqualityComparer <T> 사용에 관한 유용한 팁
chakrit

1
null과 비교할 수 있고 + nullable이 아닌 값 형식의 경우 +1로 항상 false입니다.
Jalal Said

@BlueRaja : 예. null 리터럴과 비교하기위한 특별한 규칙이 있기 때문입니다. 따라서 "제약이 없으면 null과 비교할 수 있지만 null 만 비교할 수 있습니다". 이미 답변에 있습니다. 그렇다면 왜 이것이 정확하지 않을 수 있습니까?
Jon Skeet

41

일반적으로 EqualityComparer<T>.Default.Equals구현 IEquatable<T>또는 합리적인 Equals구현 이있는 모든 작업을 수행해야합니다 .

그러나, 경우 ==Equals어떤 이유로 다르게 구현되어, 다음에 내 작품을 일반 사업자 유용합니다; 그것은 (다른 것들 중에서) 연산자 버전을 지원합니다 :

  • 같음 (T 값 1, T 값 2)
  • 같지 않음 (T value1, T value2)
  • 그레이터 탄 (T value1, T value2)
  • LessThan (T 값 1, T 값 2)
  • GreaterThanOrEqual (T 값 1, T 값 2)
  • LessThanOrEqual (T 값 1, T 값 2)

매우 흥미로운 도서관! :) (Side note : pobox 하나는 직장의 방화벽에 의해 차단 되었기 때문에 www.yoda.arachsys.com에 대한 링크를 사용하는 것이 좋습니다? 다른 사람들도 같은 문제에 직면 할 수 있습니다.)
Hosam Aly

아이디어는 즉 pobox.com/~skeet 것이다 항상 다른 곳으로 이동하는 경우에도 - 내 웹 사이트를 가리 킵니다. 나는 후손을 위해 pobox.com를 통해 링크를 게시하는 경향이있다 -하지만 당신은 할 수 현재 대신 yoda.arachsys.com를 대체합니다.
Jon Skeet

pobox.com의 문제점은 웹 기반 전자 메일 서비스 (또는 회사 방화벽이 말하는 것)이므로 차단되어 있다는 것입니다. 그래서 그 링크를 따라갈 수 없었습니다.
Hosam Aly

"그러나 어떤 이유로 ==와 같음이 다르게 구현되는 경우"-성배! 그러나 얼마나! 어쩌면 나는 반대로 유스 케이스를 볼 필요가 있지만 등가 의미가 다른 라이브러리는 제네릭 문제보다 더 큰 문제가 발생할 수 있습니다.
에드워드 브레이

@EdwardBrey 당신은 틀리지 않습니다; 컴파일러가이를 강제 할 수 있다면 좋을 것입니다.
Marc Gravell

31

하나의 답변이 아닌 많은 답변이 왜 그 이유를 설명합니까? (Giovanni가 명시 적으로 요청한) ...

.NET 제네릭은 C ++ 템플릿처럼 작동하지 않습니다. C ++ 템플릿에서는 실제 템플릿 매개 변수를 알고 난 후에 과부하 해결이 발생합니다.

.NET 제네릭 (C # 포함)에서 실제 제네릭 매개 변수를 몰라도 과부하 해결이 발생합니다. 컴파일러가 호출 할 함수를 선택하기 위해 사용할 수있는 유일한 정보는 일반 매개 변수의 유형 제한 조건에서 온 것입니다.


2
그러나 왜 컴파일러가 그것들을 일반적인 객체로 취급 할 수 없습니까? ==모든 유형에 대한 모든 작업 후에 는 참조 유형 또는 값 유형이됩니다. 그것은 당신이 대답하지 않았다고 생각하는 질문이어야합니다.
nawfal

4
@nawfal : 실제로 아니요, ==모든 값 유형에 대해 작동하지는 않습니다. 더 중요한 것은 모든 유형에 대해 동일한 의미를 갖지 않으므로 컴파일러는 해당 형식으로 무엇을해야하는지 알 수 없습니다.
벤 Voigt

1
벤, 오 예 나는 우리가 아무 것도없이 만들 수있는 커스텀 구조체를 놓쳤다 ==. 여기에 요점이 있다고 생각
되는대로

12

컴파일은 T가 구조체 (값 유형)가 될 수 없다는 것을 알 수 없습니다. 그래서 당신은 그것이 내가 생각하는 참조 유형 일 수 있다고 말해야합니다.

bool Compare<T>(T x, T y) where T : class { return x == y; }

T가 값 유형일 수있는 경우 x == y유형에 연산자 ==가 정의되어 있지 않은 경우에 잘못 형성 될 수 있기 때문 입니다. 더 명백한 이것에 대해서도 마찬가지입니다.

void CallFoo<T>(T x) { x.foo(); }

함수 foo가없는 T 타입을 전달할 수 있기 때문에 실패합니다. C #을 사용하면 가능한 모든 유형에 항상 foo 함수가 있어야합니다. 이것은 where 절에 의해 수행됩니다.


1
설명해 주셔서 감사합니다. 값 유형이 즉시 == 연산자를 지원하지 않는다는 것을 몰랐습니다.
Hosam Aly

1
Hosam, 나는 gmcs (mono)로 테스트했으며 항상 참조를 비교합니다. (즉, T는 임의로 정의하는 연산자 == 사용하지 않는다)
요하네스 SCHAUB을 - litb

이 솔루션에는 한 가지주의 사항이 있습니다. operator ==는 오버로드 될 수 없습니다. 이 StackOverflow 질문을 참조하십시오 .
Dimitri C.

8

클래스 제약이없는 것으로 보입니다.

bool Compare<T> (T x, T y) where T: class
{
    return x == y;
}

연산자 에서 class제약 Equals==받는 Object.Equals동안 구조체 로부터 상속받는 반면 구조체의 구조체는 재정의 한다는 사실을 알아야합니다 ValueType.Equals.

참고 :

bool Compare<T> (T x, T y) where T: struct
{
    return x == y;
}

또한 동일한 컴파일러 오류가 발생합니다.

아직 나는 왜 값 유형 평등 연산자 비교가 컴파일러에 의해 거부되는지 이해하지 못합니다. 나는 이것이 사실이라는 것을 알고있다.

bool Compare<T> (T x, T y)
{
    return x.Equals(y);
}

당신은 메신저 총 C # 멍청한 놈 알아요. 하지만 컴파일러가 무엇을 해야할지 모르기 때문에 실패한다고 생각합니다. T는 아직 알려지지 않았으므로 값 유형이 허용되는 경우 유형 T에 따라 수행되는 작업이 달라집니다. 참조의 경우 참조는 T에 관계없이 비교됩니다. .Equals를 수행하면 .Equal이 호출됩니다.
Johannes Schaub-litb

그러나 값 유형에 ==를 수행하면 값 유형이 해당 연산자를 구현할 필요가 없습니다.
Johannes Schaub-litb

이해가 되겠지만, litb :) 사용자 정의 구조체가 ==을 오버로드하지 않아서 컴파일러가 실패 할 수 있습니다.
Jon Limjap

2
첫 번째 비교 방법은 참조 평등을 사용 하지 않고Object.Equals 대신 테스트합니다. 예를 들어, Compare("0", 0.ToString())인수가 고유 한 문자열에 대한 참조이므로 둘 다 유일한 문자로 0을 갖기 때문에 false를 리턴합니다.
supercat

1
그 마지막에 미성년자가있어-구조체로 제한하지 않았으므로 NullReferenceException일어날 수 있습니다.
Flynn1179

6

내 경우에는 평등 연산자를 단위 테스트하고 싶었습니다. 제네릭 형식을 명시 적으로 설정하지 않고 항등 연산자에서 코드를 호출해야했습니다. 에 대한 조언은 호출 된 메소드 EqualityComparer로 도움이되지 않았지만 평등 연산자는 아닙니다.EqualityComparerEquals

다음은을 빌드하여 일반 유형으로 작업하는 방법 LINQ입니다. 올바른 연산자 ==!=연산자 를 호출합니다 .

/// <summary>
/// Gets the result of "a == b"
/// </summary>
public bool GetEqualityOperatorResult<T>(T a, T b)
{
    // declare the parameters
    var paramA = Expression.Parameter(typeof(T), nameof(a));
    var paramB = Expression.Parameter(typeof(T), nameof(b));
    // get equality expression for the parameters
    var body = Expression.Equal(paramA, paramB);
    // compile it
    var invokeEqualityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
    // call it
    return invokeEqualityOperator(a, b);
}

/// <summary>
/// Gets the result of "a =! b"
/// </summary>
public bool GetInequalityOperatorResult<T>(T a, T b)
{
    // declare the parameters
    var paramA = Expression.Parameter(typeof(T), nameof(a));
    var paramB = Expression.Parameter(typeof(T), nameof(b));
    // get equality expression for the parameters
    var body = Expression.NotEqual(paramA, paramB);
    // compile it
    var invokeInequalityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
    // call it
    return invokeInequalityOperator(a, b);
}

4

여기에 대한 MSDN Connect 항목이 있습니다.

Alex Turner의 답변은 다음과 같이 시작합니다.

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


4

사용자 정의 유형의 연산자가 호출되도록하려면 리플렉션을 통해 수행 할 수 있습니다. 일반 매개 변수를 사용하여 유형을 가져오고 원하는 연산자에 대한 MethodInfo를 검색하십시오 (예 : op_Equality, op_Inequality, op_LessThan ...).

var methodInfo = typeof (T).GetMethod("op_Equality", 
                             BindingFlags.Static | BindingFlags.Public);    

그런 다음 MethodInfo의 Invoke 메소드를 사용하여 연산자를 실행하고 오브젝트를 매개 변수로 전달하십시오.

var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2});

그러면 일반 매개 변수에 적용된 제한 조건에 의해 정의 된 연산자가 아닌 오버로드 된 연산자가 호출됩니다. 실용적이지는 않지만 몇 가지 테스트가 포함 된 일반 기본 클래스를 사용할 때 운영자 단위 테스트에 유용 할 수 있습니다.


3

최신 msdn을보고 다음 함수를 작성했습니다. 그것은 쉽게 두 개체 비교할 수 xy:

static bool IsLessThan(T x, T y) 
{
    return ((IComparable)(x)).CompareTo(y) <= 0;
}

4
부울을 제거하고 글을 쓸 수 있습니다return ((IComparable)(x)).CompareTo(y) <= 0;
aloisdg codidact.com으로 이동

1

bool Compare(T x, T y) where T : class { return x == y; }

위의 방법은 사용자 정의 참조 유형의 경우 ==를 처리하기 때문에 작동합니다.
값 유형의 경우 ==를 재정의 할 수 있습니다. 이 경우 "! ="도 정의해야합니다.

그 이유가 될 수 있다고 생각합니다. "=="를 사용한 일반적인 비교는 허용되지 않습니다.


2
감사. 참조 유형도 연산자를 재정의 할 수 있다고 생각합니다. 그러나 이제는 실패 이유가 분명합니다.
Hosam Aly

1
==토큰은 두 개의 서로 다른 통신에 사용됩니다. 주어진 피연산자 유형에 대해 동등 연산자의 호환 가능한 과부하가 있으면 해당 과부하가 사용됩니다. 그렇지 않으면 두 피연산자가 서로 호환되는 참조 유형 인 경우 참조 비교가 사용됩니다. Compare위 의 방법에서 컴파일러는 첫 번째 의미가 적용된다는 것을 알 수 없지만 두 번째 의미가 적용된다는 것을 알 수 있으므로 ==토큰은 등식 검사 연산자에 과부하가 걸리 더라도 T(예를 들어 type String) 토큰을 사용합니다 .
supercat

0

.Equals()동안 나를 위해 작동 TKey하는 일반적인 유형입니다.

public virtual TOutputDto GetOne(TKey id)
{
    var entity =
        _unitOfWork.BaseRepository
            .FindByCondition(x => 
                !x.IsDelete && 
                x.Id.Equals(id))
            .SingleOrDefault();


    // ...
}

그건 x.Id.Equals아니고 id.Equals. 아마도 컴파일러는의 유형에 대해 알고있을 것입니다 x.
Hosam Aly
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.