C# 동등성 비교

Tip

이 문서는 이미 하나 이상의 프로그래밍 언어를 알고 있으며 C#을 학습하는 개발자를 위한 기본 사항 섹션의 일부입니다. 프로그래밍을 처음 접하는 경우 먼저 시작 자습서로 시작 하세요.

다른 언어에서 오시겠습니까? Java에서 객체에 대한 == 및 JavaScript에서 객체에 대한 === 는 내용이 아니라 동일성을 검사합니다. C# 클래스는 기본적으로 동일한 방식으로 작동합니다. Python에서는 ==를 호출하며, C# __eq__가 비교되는 방식과 유사하게 기본적으로 내용을 기준으로 테스트합니다. 또한 C# 구조체는 호출 Equals할 때 값별로 비교됩니다.

C#은 두 종류의 같음을 구분합니다. 값 같음 은 데이터가 일치할 때 두 인스턴스가 같음을 의미합니다. 참조 동일성은 두 변수가 메모리에서 동일한 객체를 가리킬 때만 동일하다는 의미입니다. 이 조건을 ID라고도 합니다. 값 형식은 일반적으로 데이터를 비교하고 참조 형식은 일반적으로 ID를 비교합니다. 형식 작성자는 이러한 기본값을 변경할 수 있지만, 정신 모델은 동일하게 보이는 두 개체가 같지 않거나 한 변수를 통한 돌연변이가 다른 변수가 보는 것을 자동으로 변경하는 미묘한 버그를 방지합니다.

값 형식, 참조 형식 및 기본 동등성

C#의 모든 형식은 값 형식 또는 참조 형식입니다. 값 형식은 변수에 해당 데이터를 직접 보유합니다. 참조 형식은 개체에 대한 참조를 보유합니다. 참조 형식 변수를 다른 변수에 할당하면 두 변수 모두 동일한 개체를 참조합니다. 값 형식 및 참조 형식에 대한 자세한 내용은 형식 시스템 개요를 참조하세요.

기본 같음 동작은 일반적으로 형식의 종류를 따릅니다.

  • 기본 제공 숫자 형식 및 열거형은 값 형식입니다. 두 int 변수는 숫자 값이 일치할 때 동일합니다.
  • 구조체는 값 형식입니다. 일반적인 structEquals를 호출할 때 값 동등성을 사용합니다.
  • 튜플은 값 형식입니다. 두 튜플은 모든 요소 값이 일치할 때 동일합니다.
  • 클래스는 참조 형식입니다. 일반 클래스는 참조 같음을 사용하므로 ==Equals 두 변수가 동일한 개체를 가리키는지 테스트합니다.

클래스는 참조 동일성을 사용합니다. 데이터가 같은 두 개의 개별 개체는 같지 않지만 동일한 개체를 참조하는 두 개의 변수는 같습니다.

var order1 = new Order(42, "Shoes");
var order2 = new Order(42, "Shoes");

Console.WriteLine(order1 == order2);               // => False
Console.WriteLine(order1.Equals(order2));          // => False
Console.WriteLine(ReferenceEquals(order1, order2)); // => False

Order order3 = order1;
Console.WriteLine(order1 == order3);               // => True

A는 struct 을 통해 Equals값 같음을 표시합니다. 두 구조체 인스턴스는 필드가 일치할 때 동일합니다.

var pt1 = new Point(3, 4);
var pt2 = new Point(3, 4);

Console.WriteLine(pt1.Equals(pt2)); // => True

일반 구조체는 미리 정의된 == 연산자를 얻지 못합니다. 일반 구조체에 p1 == p2를 작성하는 것은 해당 구조체가 자체 operator ==를 선언하는 경우에만 컴파일됩니다. 구조체에 대한 비교 연산자가 필요한 경우 ==!=를 한 쌍으로 정의하고 EqualsGetHashCode와 일관되게 유지하세요.

튜플도 값 형식입니다. 모든 요소 값이 일치하면 두 개의 튜플이 동일합니다. 명명된 튜플의 요소 이름은 컴파일 시간 편의성이며 비교하는 동안 고려되지 않습니다. 위치와 값만 중요합니다.

var t1 = (Name: "Grace", Role: "Engineer");
var t2 = (Name: "Grace", Role: "Engineer");

Console.WriteLine(t1 == t2); // => True

튜플 구문 및 분해에 대한 자세한 내용은 튜플 및 분해를 참조하세요.

ID를 직접 테스트하는 데 사용 Object.ReferenceEquals

ReferenceEquals는 형식이 Equals를 재정의하거나 ==를 오버로드하는 방식과 관계없이 항상 동일성을 테스트합니다. 두 변수가 정확히 동일한 개체를 가리키는지 확인해야 하는 경우 ID 진단으로 사용합니다.

var doc1 = new Document("Report");
var doc2 = new Document("Report");
var doc3 = doc1;

Console.WriteLine(ReferenceEquals(doc1, doc2)); // => False
Console.WriteLine(ReferenceEquals(doc1, doc3)); // => True

일반적으로는 Equals 재정의에서 전체 비교를 생략하는 용도로 사용됩니다. 두 인수가 동일한 참조이면 개별 필드를 확인하지 않아도 항상 같은 것으로 간주됩니다.

메모

변수를 인터페이스로 입력하는 경우 인터페이스== 변수가 동일한 개체를 참조하는지 여부를 확인합니다. Equals에 대한 호출은 여전히 기본 개체의 구현을 실행합니다.

메모

ReferenceEquals 두 인수에 동일한 값이 포함되어 있더라도 값 형식을 비교할 때 항상 반환 false 됩니다. 이 동작은 각 값 형식 인수가 ReferenceEquals에 전달될 때 각각 별도의 힙 객체로 박싱되기 때문에 발생합니다.

형식은 서로 다른 같음 의미 체계를 정의할 수 있습니다.

형식은 기본 동작과 다른 같음 의미 체계를 정의할 수 있습니다 . 가장 일반적인 이유는 값 같음을 구현하기 위해서입니다. 은행 계좌, 인벤토리의 제품 또는 시스템의 사용자와 같은 데이터를 나타내는 형식을 만드는 경우 동일한 값을 가진 인스턴스를 고려합니다. 값 같음을 구현하기 위한 레코드 형식 을 선택하면 컴파일러가 필요한 모든 같음 멤버를 생성합니다.

메모

문자열은 클래스이지만 ==Equals ID가 아닌 문자열 콘텐츠를 비교합니다.

  • ==: 같음 연산자입니다. 대부분의 형식은 이 연산자를 기본 같음 검사로 사용합니다. 해당 동작은 형식에 기본 제공 연산자 또는 사용자 정의 == 연산자가 있는지 여부에 따라 달라집니다.
  • !=: 같지 않음 연산자입니다. 형식이 사용자 정의 == 연산자를 정의하는 경우 !=도 정의해야 합니다.
  • Equals: 모든 형식에서 상속되는 가상 메서드입니다. 형식에 대한 같음 의미 체계를 변경하도록 재정의할 수 있습니다.
  • GetHashCode: 해시 기반 컬렉션에서 사용하는 가상 메서드입니다. 두 값이 같으면 해시 코드도 같아야 합니다.
  • ReferenceEquals: 항상 ID를 테스트하는 정적 메서드입니다.

값 같음을 위해 레코드 사용

형식이 레코드일 수 있는 경우 데이터 중심 형식 값 동등성을 제공하려면 record 한정자를 사용합니다. 컴파일러는 선언된 모든 속성 값을 비교하는 멤버를 생성EqualsGetHashCode합니다==/!=.

A record class 는 여전히 참조 형식이지만 ID 대신 값을 비교합니다.

var person1 = new Person("Ada", "Lovelace");
var person2 = new Person("Ada", "Lovelace");

Console.WriteLine(person1 == person2);               // => True
Console.WriteLine(person1.Equals(person2));          // => True
Console.WriteLine(ReferenceEquals(person1, person2)); // => False

ReferenceEqualsperson1person2가 메모리상에서 서로 다른 객체임을 확인하는 반면, ==Equals는 컴파일러가 생성한 동등성 비교가 속성 값을 비교하므로 True를 반환합니다.

동일한 컴파일러 생성 방식이 record struct 형식에도 적용됩니다:

var dim1 = new Dimension(1920, 1080);
var dim2 = new Dimension(1920, 1080);

Console.WriteLine(dim1 == dim2);      // => True
Console.WriteLine(dim1.Equals(dim2)); // => True

레코드 형식은 자체 형식에 대한 전체 같음 집합을 생성합니다. record classrecord struct 형식은 모두 EqualsGetHashCode를 재정의합니다. 또한 ==!= 연산자와 레코드 형식에 대한 형식이 지정된 Equals 메서드도 생성합니다. 따라서 일반 struct와 달리 record struct==!=를 자동으로 지원합니다. 레코드 형식 및 해당 같음 의미 체계에 대한 자세한 내용은 레코드를 참조하세요.

참조 형식 멤버가 있는 레코드

레코드의 동등성은 멤버 자체의 동등성 의미 체계를 사용합니다. 각 속성 또는 필드는 자체 Equals 메서드를 사용하여 비교됩니다. 대부분의 스칼라 값(예: int, string또는 DateTime)의 경우 이 방법은 레코드 멤버의 값을 비교합니다. 이러한 형식은 참조별로 비교되는 일반적인 변경 가능한 컬렉션 List<T>T[]에서 미묘하게 발생하므로 동일한 콘텐츠를 가진 서로 다른 목록 개체 를 포함하는 두 레코드 인스턴스는 합성된 레코드 같음으로 간주되지 않습니다 .

var playlist1 = new Playlist("Chill", new List<string> { "Song A", "Song B" });
var playlist2 = new Playlist("Chill", new List<string> { "Song A", "Song B" });

Console.WriteLine(playlist1.Equals(playlist2));                        // => False (different List instances)
Console.WriteLine(playlist1.Tracks.SequenceEqual(playlist2.Tracks));   // => True

playlist1playlist2 별도의 List<string> 인스턴스입니다. 내용이 일치하더라도 Equals은(는) false을(를) 반환합니다.

레코드 동등성이 컬렉션 내용을 반영해야 하는 경우 몇 가지 선택지가 있습니다:

  • 레코드에 IEquatable<T>를 구현하고 컬렉션 멤버에 Equals를 사용하도록 Enumerable.SequenceEqual를 재정의합니다.
  • 값이 같은 컬렉션 형식을 사용합니다 (예: 사용자 지정 IEqualityComparer<T> 또는 고유한 Equals 요소가 비교되는 형식).
  • ID를 중심으로 디자인: 레코드가 순수 값이 아닌 엔터티를 나타내는 경우 컬렉션 멤버에 대한 참조 같음이 의도적인 것일 수 있습니다.

Important

오늘날 C#에서 동등성을 직접 구현하는 경우는 드뭅니다. 레코드는 값 같음의 일반적인 시나리오를 자동으로 처리합니다. 예를 들어 형식이 레코드가 아닌 기본 클래스에서 파생되어야 하기 때문에 같음을 수동으로 구현해야 하는 경우 언어 참조에서 형식이 레코드가 될 수 없는 경우 직접 같음 구현 을 참조하세요.

참고하십시오