(object) 0 == (object) 0이 ((object) 0) .Equals ((object) 0)과 다른 이유는 무엇입니까?


117

다음 표현이 다른 이유는 무엇입니까?

[1]  (object)0 == (object)0 //false
[2]  ((object)0).Equals((object)0) // true

실제로 .NET 런타임이 box정수를 사용하고 참조 비교를 시작 하기 때문에 [1]을 완전히 이해할 수 있습니다 . 그러나 [2]가 다른 이유는 무엇입니까?


36
이제이 질문에 대한 답을 이해 short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); 했으니 다음의 결과를 예측하여 이해를 확인하십시오. 이제 현실과 비교해보십시오. 당신의 예측이 맞습니까? 그렇지 않다면 불일치를 설명 할 수 있습니까?
Eric Lippert

1
@Star, 권장 읽기는 msdn.microsoft.com/en-us/library/vstudio/… 에서 int16일명 shortEquals 메서드 에서 사용 가능한 오버로드를 확인한 다음 msdn.microsoft.com/en-us/library/ms173105.aspx 를 참조 하십시오. . 나는 Eric Lippert의 퍼즐을 망치고 싶지 않지만 일단 그 페이지를 읽으면 이해하기가 꽤 쉬울 것입니다.
Sam Skuce

2
나는 이것이 자바 질문이라고 생각했다. 적어도 Equals에서 'E'를보기 전에.
seteropere

4
@seteropere Java는 실제로 다릅니다. Java의 오토 박싱은 객체를 캐시하므로 ((Integer)0)==((Integer)0)true로 평가됩니다.
Jules

1
시도해 볼 수도 있습니다 IFormattable x = 0; bool test = (object)x == (object)x;. 구조체가 이미 상자에있는 경우 새 권투가 수행되지 않습니다.
Jeppe Stig Nielsen

답변:


151

호출이 다르게 작동하는 이유는 매우 다른 메서드에 바인딩되기 때문입니다.

==경우 정적 기준 항등 연산자로 결합한다. 2 개의 독립적 인 박스형 int값이 생성되었으므로 동일한 참조가 아닙니다.

두 번째 경우에는 인스턴스 메서드에 바인딩합니다 Object.Equals. 이것은 필터링되는 가상 방법 Int32.Equals이며 이것은 박스형 정수를 확인합니다. 두 정수 값은 모두 0이므로 동일합니다.


==경우는 호출하지 않습니다 Object.ReferenceEquals. 단순히 ceq참조 비교를 수행하기 위해 IL 명령어를 생성합니다 .
Sam Harwell

8
@ 280Z28은 컴파일러가 인라인하기 때문이 아닙니다.
markmnl

@ 280Z28 그래서? 유사한 경우는 Boolean.ToString 메서드가 공개적으로 노출 된 Boolean.TrueString 및 Boolean.FalseString을 반환하는 대신 함수 내부에 하드 코딩 된 문자열을 포함하는 것입니다. 관련이 없습니다. 요점은 (어쨌든 Object에서) ==와 같은 일을한다는 것 ReferenceEquals입니다. 자주 사용되는 함수에 대한 불필요한 내부 함수 호출을 피하기 위해 MS 측에서 모두 내부 최적화입니다.
Nyerguds

6
C # 언어 사양, 7.10.6 단락은 다음과 같이 말합니다. 미리 정의 된 참조 유형 같음 연산자는 다음과 같습니다 bool operator ==(object x, object y); bool operator !=(object x, object y);. 연산자 는 같거나 같지 않은 두 참조를 비교 한 결과를 반환합니다. System.Object.ReferenceEquals결과를 결정하기 위해 방법 을 사용해야 하는 것은 아닙니다 . @markmnl에게 : 아니요, C # 컴파일러는 인라인하지 않습니다. 이는 지터가 때때로 수행하는 작업입니다 (이 경우에는 아님). 따라서 280Z28이 옳은 ReferenceEquals방법은 실제로 사용되지 않습니다.
Jeppe Stig Nielsen

@JaredPar : 사양이 그렇게 말하는 것이 흥미 롭습니다. 언어가 실제로 작동하는 방식이 아니기 때문입니다. 위와 같이 정의 된 연산자와 변수 Cat Whiskers; Dog Fido; IDog Fred;(비 관련 인터페이스 ICatIDog비 관련 클래스 Cat:ICatDog:IDog)가 주어지면 비교 Whiskers==FidoWhiskers==34합법적입니다 (첫 번째는 Whiskers와 Fido가 모두 null 인 경우에만 true 일 수 있으며 두 번째는 true 일 수 없음 ). 실제로 C # 컴파일러는 둘 다 거부합니다. 봉인 Whiskers==Fred;된 경우 금지 Cat되지만 그렇지 않은 경우 허용됩니다.
supercat dec

26

int 값 0(또는 다른 값 유형)을로 캐스팅 object하면 값이 boxed 됩니다. 각 캐스트 object는 다른 상자 (즉, 다른 개체 인스턴스) 를 생성합니다. ==위한 오퍼레이터 object입력을 수행하는 기준 비교하여, 왼쪽 및 오른쪽은 동일한 인스턴스 아니므 false를 반환하므로.

반면에 Equals가상 메소드 인를 사용하면 실제 boxed 유형의 구현을 사용합니다. 즉 Int32.Equals, 두 객체가 동일한 값을 갖기 때문에 true를 반환합니다.


18

==작업자는 고정되고, 가상이 아니다. 정확한 코드를 실행합니다.object클래스가 정의 (`객체는 피연산자의 컴파일 시간 유형 임)를 실행하여 두 객체의 런타임 유형에 관계없이 참조 비교를 수행합니다.

Equals방법은 가상 인스턴스 메소드이다. object클래스 의 코드가 아닌 (첫 번째) 개체의 실제 런타임 유형에 정의 된 코드를 실행합니다 . 이 경우 객체는 int이므로 int유형이 Equals메서드에 대해 정의 하는 값 비교를 수행합니다 .


==토큰은 실제로 두 overloadable 중 하나는 사업자 중 하나가 아닌를 나타냅니다. 두 번째 연산자의 동작은 (object, object)에 대한 과부하 동작과 매우 다릅니다.
supercat

13

Equals()방법은 가상입니다.
따라서 callsite가 캐스트되는 경우에도 항상 구체적인 구현을 호출합니다 object. 값으로 비교하도록 int재정의 Equals()하므로 값 비교를 얻을 수 있습니다.


10

== 사용하다: Object.ReferenceEquals

Object.Equals 값을 비교합니다.

그만큼 object.ReferenceEquals 메서드는 참조를 비교합니다. 개체를 할당 할 때 메모리 힙에있는 개체의 데이터 외에 메모리 위치를 나타내는 값이 포함 된 참조를받습니다.

object.Equals메서드는 개체의 내용을 비교합니다. 먼저 object.ReferenceEquals와 마찬가지로 참조가 동일한 지 확인합니다. 그러나 그런 다음 파생 된 Equals 메서드를 호출하여 동등성을 추가로 테스트합니다. 이것 좀 봐:

   System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

피연산자에 Object.ReferenceEqualsC # ==연산자 를 사용하는 메서드처럼 동작 하지만 C # 참조 같음 연산자 연산자 ( ==오버로드가 정의되지 않은 피연산자 유형에 대해 사용하여 표시됨)는를 호출하는 대신 특수 명령어를 사용합니다 ReferenceEquals. 또한, Object.ReferenceEquals둘 다 null 인 경우에만 일치 할 수있는 피연산자를 허용하고 유형 강제 변환이 필요 Object하므로 어떤 것도 일치시킬 수없는 피연산자를 허용 하지만 참조 같음 버전은 ==이러한 사용을 컴파일하지 않습니다. .
supercat dec

9

C # 연산자는 토큰 ==을 사용하여 두 개의 다른 연산자, 즉 정적으로 오버로드 가능한 비교 연산자와 오버로드 불가능한 참조 비교 연산자를 나타냅니다. ==토큰을 발견하면 먼저 피연산자 유형에 적용 할 수있는 동등성 테스트 오버로드가 있는지 확인합니다. 그렇다면 해당 오버로드를 호출합니다. 그렇지 않으면 유형이 참조 비교 연산자에 적용 가능한지 여부를 확인합니다. 그렇다면 해당 연산자를 사용합니다. 피연산자 유형에 적용 할 수있는 연산자가 없으면 컴파일이 실패합니다.

이 코드 (Object)0는 모든 값 유형과 마찬가지로 Int32to Object:를 업 캐스트하지 않습니다 . Int32실제로 두 유형을 나타냅니다.이 유형 중 하나는 값과 저장 위치 (예 : 리터럴 0)를 설명하지만 아무것도 파생되지 않으며 그 중 하나는 설명합니다. 힙 개체 및 파생 Object: 후자의 유형 만으로 업 캐스트 될 수 있으므로 Object컴파일러는 후자의 유형의 새 힙 객체를 만들어야합니다. 를 호출 할 때마다 (Object)0새 힙 개체 가 만들어 지므로 두 피연산자 ==는 서로 다른 개체이며 각각 독립적으로 Int32값 0을 캡슐화합니다 .

클래스 Object에는 같음 연산자에 대해 정의 된 사용 가능한 오버로드가 없습니다. 결과적으로 컴파일러는 오버로드 된 같음 테스트 연산자를 사용할 수 없으며 참조 같음 테스트 사용으로 돌아갑니다. 두 피연산자가 ==별개의 개체 를 참조하기 때문에 false. 두 번째 비교는 하나의 힙 오브젝트 인스턴스가 Int32다른 것과 동일한 지 묻기 때문에 성공합니다 . 해당 인스턴스는 다른 개별 인스턴스와 동일하다는 것이 무엇을 의미하는지 알고 있기 때문에 true.


또한 0코드에 리터럴을 작성할 때마다 힙에 int 개체를 만든다고 가정합니다. 하나의 전역 정적 0 값에 대한 고유 한 참조가 아닙니다 (새 문자열을 초기화하기 위해 새 빈 문자열 개체를 만드는 것을 피하기 위해 String.Empty를 만든 방법과 같이). 따라서 0.ReferenceEquals(0)두 0이 모두 false를 반환하기 때문에 a 를 수행해도 false가 반환됩니다. 새로 생성 된 Int32개체.
Nyerguds

1
@Nyerguds, 0.ReferenceEquals(0)컴파일 시간 상수에 대한 메서드를 호출하려고하기 때문에 int, heap, history, global statics 등에 대해 말한 모든 것이 잘못되었다고 확신합니다 . 걸 수있는 물건이 없습니다. unboxed int는 스택에 저장된 구조체입니다. 심지어 int i = 0; i.ReferenceEquals(...)작동하지 않습니다. System.Int32에서 상속하지 않기 때문 입니다 Object.
앤드류 후원자

@AndrewBacker System.Int32struct, a structis System.ValueType이며 System.Object. 이 Thatswhy ToString()방법 및 Equals대한 방법System.Int32
세바스찬은

1
그럼에도 불구하고 Nyerguds는 Int32가 힙에 생성 될 것이라고 말하는 것은 잘못되었습니다.
Sebastian

@SebastianGodelet, 나는 내부를 무시하고 있습니다. System.Int32 자체는 이러한 메서드를 구현합니다. GetType ()은 extern on Object이며 오래 전에 걱정을 멈춘 곳입니다. 더 멀리 갈 필요가 없었습니다. AFAIK CLR은 두 가지 유형을 다르게 처리합니다. 상속 만이 아닙니다. 하지만 is두 가지 유형의 데이터 중 하나입니다. 나는 단지 누군가가 그 주석을 읽고 문자열 인턴을 무시하는 빈 문자열에 대한 이상한 점을 포함하여 너무 멀리 떨어져 있기를 원하지 않았습니다.
Andrew Backer

3

두 수표가 모두 다릅니다. 위한 첫 번째 수표 의 신원 에 대한 두번째 같음 . 일반적으로 동일한 객체를 참조하는 경우 두 용어는 동일합니다. 이것은 그들이 동등하다는 것을 의미합니다. 값이 같으면 두 항은 같습니다.

프로그래밍의 관점에서 정체성은 일반적으로 참조 평등에 의해 엉망입니다. 두 용어에 대한 포인터가 같으면 (!), 그들이 가리키는 개체는 정확히 같은 것입니다. 그러나 포인터가 다른 경우 포인터가 가리키는 개체의 값은 여전히 ​​동일 할 수 있습니다. C #에서는 정적 Object.ReferenceEquals멤버를 사용하여 ID를 확인할 수 있으며 비 정적 Object.Equals멤버를 사용하여 동등성을 확인할 수 있습니다 . 두 개의 정수를 객체에 캐스팅하므로 ( "boxing", btw라고 함)의 피연산자 는 기본적으로 매핑되고 ID 를 확인하는 첫 번째 검사 ==object수행합니다 Object.ReferenceEquals. 비 정적 Equals멤버 를 명시 적으로 호출하는 경우 동적 디스패치 는를 호출하여 Int32.Equals동등성을 확인합니다.

두 개념은 비슷하지만 같지는 않습니다. 처음에는 혼란스러워 보일 수 있지만 작은 차이가 매우 중요합니다! "앨리스"와 "밥"이라는 두 사람을 상상해보십시오. 둘 다 노란 집에 살고 있습니다. 앨리스와 밥이 주택 색깔 만 다른 지역에 살고 있다는 가정에 따르면 둘 다 다른 노란색 주택에 살 수 있습니다. 두 집을 비교하면 두 집이 모두 노란색이기 때문에 절대적으로 동일하다는 것을 알 수 있습니다! 그러나 그들은 같은 집을 공유하지 않기 때문에 그들의 집은 동일 하지만 동일 하지는 않습니다 . 정체성은 그들이 같은 집에 살고 있음을 의미합니다 .

참고 : 일부 언어는 ===ID를 확인하기 위해 연산자를 정의합니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.