[C#] C #에서 ==와! =를 모두 정의해야하는 이유는 무엇입니까?

C # 컴파일러는 사용자 정의 유형이 operator를 정의 할 때마다 정의 ==해야합니다 !=( 여기 참조 ).

왜?

디자이너가 왜 필요하다고 생각했는지 그리고 왜 다른 사람 만 존재할 때 컴파일러가 운영자 중 하나를 위해 합리적인 구현을 기본으로 할 수 없는지 알고 싶습니다. 예를 들어 Lua를 사용하면 항등 연산자 만 정의하고 다른 연산자는 무료로 얻을 수 있습니다. C #은 == 또는 == 및! =를 모두 정의한 다음 누락 된! = 연산자를로 자동 컴파일하여 동일한 작업을 수행 할 수 !(left == right)있습니다.

IEEE-754 NaN과 같이 일부 엔터티가 같거나 같지 않은 이상한 경우가 있지만 예외가 아닌 예외처럼 보입니다. 따라서 C # 컴파일러 디자이너가 예외를 규칙으로 만든 이유는 설명하지 않습니다.

평등 연산자가 정의 된 솜씨가 좋지 않은 경우를 보았습니다. 불평등 연산자는 모든 비교가 역전되고 모든 &&가 || (당신은 요점을 얻습니다 … 기본적으로! (a == b) De Morgan의 규칙을 통해 확장되었습니다). Lua의 경우와 마찬가지로 컴파일러가 의도적으로 설계를 통해 제거 할 수있는 것은 좋지 않습니다.

참고 : 연산자 <> <=> =도 마찬가지입니다. 부 자연스러운 방법으로 정의 해야하는 경우를 상상할 수 없습니다. Lua를 사용하면 <와 <= 만 정의하고 전자의 부정을 통해 자연스럽게> =와>를 정의 할 수 있습니다. C #이 왜 똑같이하지 않습니까 (적어도 ‘기본적으로’)?

편집하다

프로그래머가 원하는대로 평등과 불평등에 대한 검사를 구현할 수있는 정당한 이유가있는 것 같습니다. 대답 중 일부는 그것이 좋은 경우를 지적합니다.

그러나 내 질문의 핵심은 일반적 으로 논리적으로 필요 하지 않은 C #에서 이것이 강제적으로 필요한 이유는 무엇 입니까?

그것은 .NET 인터페이스 등에 선택을 설계하는 현저한 대조도 Object.Equals, IEquatable.Equals IEqualityComparer.Equalsa의 부족 어디 NotEquals프레임 워크 간주하는 대응 쇼 !Equals()불평등 한 물체를하고있어. 또한, 클래스와 같은 클래스 Dictionary와 메소드 .Contains()는 위에서 언급 한 인터페이스에만 의존하며 정의 된 경우에도 연산자를 직접 사용하지 않습니다. ReSharper에서 평등 멤버를 생성 할 때 사실, 그것은 모두 정의 ==!=측면에서 Equals()그렇다하더라도 사용자 선택한다면 모두에서 운영자를 생성 할 경우에만합니다. 프레임 워크에서는 객체 동등성을 이해하기 위해 동등 연산자가 필요하지 않습니다.

기본적으로 .NET 프레임 워크는 이러한 연산자를 신경 쓰지 않고 몇 가지 Equals방법 만 신경 씁니다 . == 및! = 연산자를 사용자가 함께 정의해야한다는 결정은 순수한 언어 설계와 관련이 있으며 .NET에 관한 한 객체 의미는 아닙니다.



답변

언어 디자이너를 위해 말할 수는 없지만 내가 추론 할 수있는 것은 의도적이고 적절한 디자인 결정 인 것 같습니다.

이 기본 F # 코드를 살펴보면이를 작업 라이브러리로 컴파일 할 수 있습니다. 이것은 F #에 대한 법적 코드이며 불평등이 아닌 항등 연산자에만 과부하를줍니다.

module Module1

type Foo() =
    let mutable myInternalValue = 0
    member this.Prop
        with get () = myInternalValue
        and set (value) = myInternalValue <- value

    static member op_Equality (left : Foo, right : Foo) = left.Prop = right.Prop
    //static member op_Inequality (left : Foo, right : Foo) = left.Prop <> right.Prop

이것은 정확히 어떻게 보이는지 수행합니다. 동등 비교기 ==만 작성 하고 클래스의 내부 값이 같은지 확인합니다.

C #에서는 이와 같은 클래스를 만들 수 없지만 .NET 용으로 컴파일 된 클래스 사용할 있습니다. 오버로드 된 연산자를 사용할 것이므로 == 런타임은 !=무엇을 사용 합니까?

C # EMCA 표준에는 등식을 평가할 때 사용할 연산자를 결정하는 방법을 설명하는 전체 규칙이 있습니다 (섹션 14.9). 지나치게 단순화되어 완벽하게 정확하지 않게하기 위해 비교중인 유형이 동일한 유형 이고 오버로드 된 동등 연산자가있는 경우 Object에서 상속 된 표준 참조 동등 연산자가 아닌 해당 오버로드를 사용합니다. 따라서 연산자 중 하나만 존재하는 경우 기본 참조 동등 연산자를 사용하고 모든 객체에 대해 과부하가없는 것은 놀라운 일이 아닙니다. 1

이것이 사실이라는 것을 알면 실제 질문은 다음과 같습니다. 왜 이런 식으로 설계 되었는가? 그리고 왜 컴파일러가 스스로 알아 내지 못합니까? 많은 사람들이 이것이 디자인 결정이 아니라고 말하지만, 특히 모든 객체에 기본 항등 연산자가 있다는 사실과 관련하여 이것이 그렇게 생각되었다고 생각합니다.

그렇다면 컴파일러가 !=연산자를 자동으로 생성하지 않는 이유는 무엇입니까? Microsoft의 누군가가 이것을 확인하지 않으면 확실하지 않지만 사실에 대한 추론에서 결정할 수 있습니다.


예기치 않은 동작을 방지하려면

아마도 ==평등을 테스트하기 위해 값을 비교하고 싶습니다 . 그러나 그것이 왔을 때 !=참조가 같지 않으면 값이 같은지 전혀 신경 쓰지 않았습니다. 프로그램이 그 값을 동일하게 간주하기 때문에 참조가 일치하는 경우에만 관심이 있습니다. 결국 이것은 실제로 C #의 기본 동작으로 요약됩니다 (다른 언어로 작성된 일부 .net 라이브러리의 경우와 같이 두 연산자가 모두 오버로드되지 않은 경우). 컴파일러가 자동으로 코드를 추가했다면 더 이상 호환되는 코드를 출력하기 위해 컴파일러에 의존 할 수 없었습니다. 컴파일러는 특히 사용자가 작성한 코드가 C # 및 CLI의 표준 내에있는 경우 동작을 변경하는 숨겨진 코드를 작성해서는 안됩니다.

그것의 측면에서 강제 대신 기본 동작에 가고, 그것은 과부하, 난 단지 단단히 표준 (EMCA-334 17.9.2)에 있다고 말할 수있다 2 . 표준은 이유를 명시하지 않습니다. 나는 이것이 C #이 C ++에서 많은 행동을 빌리기 때문이라고 생각합니다. 이에 대한 자세한 내용은 아래를 참조하십시오.


!=과 를 재정의하면 ==부울을 반환 할 필요가 없습니다.

이것은 또 다른 이유입니다. C #에서이 함수는 다음과 같습니다.

public static int operator ==(MyClass a, MyClass b) { return 0; }

이것만큼 유효합니다 :

public static bool operator ==(MyClass a, MyClass b) { return true; }

bool 이외의 것을 반환하면 컴파일러 자동으로 반대 유형을 유추 할 수 없습니다 . 또한 연산자 부울을 반환 하는 경우에는 특정 사례에만 존재하는 코드를 생성하거나 위에서 언급했듯이 CLR의 기본 동작을 숨기는 코드를 생성하는 것이 의미가 없습니다.


C #은 C ++ 3 에서 많은 돈을 빌린다

C #이 소개되었을 때 MSDN 잡지에 C #에 관한 기사가 실 렸습니다.

많은 개발자들은 Visual Basic처럼 쉽게 작성하고 읽고 유지 관리 할 수있는 언어가 있었지만 여전히 C ++의 기능과 유연성을 제공하기를 원했습니다.

그렇습니다. C #의 디자인 목표는 C ++과 거의 동일한 양의 전력을 공급하는 것이 었으며, 엄격한 형식 안전성 및 가비지 수집과 같은 편의를 위해 약간만 희생했습니다. C #은 C ++ 이후에 강력하게 모델링되었습니다.

이 예제 프로그램 에서 보듯 C ++에서 항등 연산자는 bool을 반환하지 않아도 된다는 사실에 놀라지 않을 것입니다.

이제 C ++를 직접하지 않습니다 필요로 보완 연산자를 오버로드 할 수 있습니다. 예제 프로그램에서 코드를 컴파일 한 경우 오류없이 코드가 실행되는 것을 볼 수 있습니다. 그러나 라인을 추가하려고 시도한 경우 :

cout << (a != b);

당신은 얻을 것이다

컴파일러 오류 C2678 (MSVC) : 이진 ‘! =’: ‘Test’유형의 왼쪽 피연산자를 취하는 연산자를 찾을 수 없습니다 (또는 허용되는 변환이 없음)`.

C ++ 자체가 쌍으로 오버로드 할 필요가 없습니다 동안 그래서, 그것은 하지 않습니다 당신이 사용자 정의 클래스에 오버로드되지 않았 음을 동등 연산자를 사용 할 수 있습니다. 모든 개체에는 기본 개체가 있으므로 .NET에서 유효합니다. C ++은 그렇지 않습니다.


1. 부수적으로, C # 표준에서는 어느 한쪽에 과부하를 가하려는 경우 여전히 한 쌍의 연산자에 과부하가 걸리도록 요구합니다. 이것은 단순한 컴파일러가 아닌 표준 의 일부입니다 . 그러나 호출 할 연산자 결정에 관한 동일한 규칙은 동일한 요구 사항이없는 다른 언어로 작성된 .net 라이브러리에 액세스 할 때 적용됩니다.

2. EMCA-334 (pdf) ( http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf )

3. 그리고 Java, 그러나 그것은 실제로 여기서 중요한 것은 아닙니다.


답변

누군가가 3 값 논리 (즉 null) 를 구현 해야하는 경우에 적합합니다 . 예를 들어 ANSI 표준 SQL과 같은 경우에는 입력에 따라 연산자를 부정 할 수 없습니다.

다음과 같은 경우가있을 수 있습니다.

var a = SomeObject();

그리고 a == true되돌아 falsea == false도 돌아갑니다 false.


답변

C #이 많은 분야에서 C ++을 연기하는 것 외에, 내가 생각할 수있는 가장 좋은 설명은 경우에 따라 “평등”을 증명하는 것과 “평등하지 않음”을 증명 하는 약간 다른 접근법을 원할 수 있다는 것입니다.

예를 들어 문자열 비교를 사용 return하면 일치하지 않는 문자가 표시 될 때 동등성 및 루프 외부를 테스트 할 수 있습니다 . 그러나 더 복잡한 문제로 인해 그렇게 깨끗하지 않을 수 있습니다. 꽃 필터는 마음에 온다; 매우 요소가있는 경우 신속하게 말할 쉽게 하지 요소가 있다면 알려 세트에, 그러나 어려운 입니다 세트에. 동일한 return기술을 적용 할 수 있지만 코드가 예쁘지 않을 수 있습니다.


답변

.net 소스에서 == 및! =의 오버로드 구현을 보면 종종! = as! (left == right)를 구현하지 않습니다. 그들은 부정 논리로 그것을 완전히 구현합니다 (= =와 같은). 예를 들어 DateTime은 ==를 다음과 같이 구현합니다.

return d1.InternalTicks == d2.InternalTicks;

그리고! = as

return d1.InternalTicks != d2.InternalTicks;

당신 (또는 컴파일러가 암시 적으로 수행 한 경우)이라면! = as

return !(d1==d2);

그런 다음 클래스가 참조하는 것들에서 == 및! =의 내부 구현에 대해 가정합니다. 그러한 가정을 피하는 것은 그들의 결정 뒤에 철학이 될 수 있습니다.


답변

하나를 재정의하면 둘 다 재정의 해야하는 이유와 관련하여 편집에 응답하려면 모두 상속됩니다.

==를 재정의하는 경우 일종의 의미 또는 구조적 동등성을 제공 할 가능성이 높습니다 (예를 들어, InternalTicks 속성이 다른 인스턴스를 통해도 동일한 경우 DateTimes는 동일 함). 모든 .NET 개체의 부모 인 개체입니다. == 연산자는 C #에서 기본 구현 Object.operator (==)가 참조 비교를 수행하는 메소드입니다. Object.operator (! =)는 다른 비교 방법이며 참조 비교도 수행합니다.

거의 모든 다른 방법을 재정의하는 경우, 하나의 방법을 재정의하면 반의어 적 방법에 대한 행동 변화가 발생한다고 가정하는 것은 비논리적입니다. Increment () 및 Decrement () 메서드를 사용하여 클래스를 만들고 자식 클래스에서 Increment ()를 재정의하는 경우 재정의 된 동작과 반대로 Decrement ()가 재정의 될 것으로 예상하십니까? 컴파일러는 모든 가능한 경우 연산자의 구현을 위해 역함수를 생성 할만큼 똑똑하게 만들 수 없습니다.

그러나 연산자는 메서드와 매우 유사하게 구현되지만 개념적으로 쌍으로 작동합니다. == 및! =, <및> 및 <= 및> =. 이 경우 소비자의 관점에서! =는 ==와 다르게 작동한다고 생각하는 것은 비논리적입니다. 따라서 컴파일러는 모든 경우에 a! = b ==! (a == b)라고 가정 할 수 없지만 일반적으로 ==와! =는 비슷한 방식으로 작동해야하므로 컴파일러는 쌍으로 구현해야하지만 실제로 그렇게합니다. 클래스의 경우 a! = b ==! (a == b) 인 경우! (==)를 사용하여! = 연산자를 구현하면됩니다. 그러나 해당 규칙이 객체의 모든 경우에 적용되지 않는 경우 (예 : 같거나 같지 않은 특정 값과의 비교가 유효하지 않은 경우 IDE보다 똑똑해야합니다.

실제 질문은 숫자 용어로! (a <b) == a> = b 및! (a> 인 경우 <및> 및 <= 및> =이 비교 연산자의 쌍인 이유입니다. b) == a <= b. 하나를 재정의하는 경우 네 가지를 모두 구현해야하며 a가 의미 적으로 (a <= b) == (a == b)이므로 == (및! =)도 재정의해야합니다. b와 같습니다.


답변

! =가 아닌 사용자 정의 유형에 대해 ==를 오버로드하면 모든 것이 객체에서 파생되므로! = 객체의! = 객체에 의해 처리됩니다. 이는 CustomType! = CustomType과 크게 다릅니다.

또한 언어 작성자는 코더에게 최대한의 유연성을 제공하고 사용자가 수행하려는 작업에 대해 가정하지 않도록이 방법을 원했을 것입니다.


답변

이것이 내 마음에 먼저 오는 것입니다.

  • 불평등 테스트가 평등 테스트보다 훨씬 빠르면 어떻게해야 합니까?
  • 어떤 경우에 대해 와를 모두 반환하려면false==!= 어떻게해야합니까 (즉, 어떤 이유로 비교할 수없는 경우)