같음 연산자 - 두 개체가 같은지 여부를 테스트합니다.

==(같음)!=(같지 않음) 연산자는 피연산자가 같은지 여부를 확인합니다. 값 형식은 내용이 같을 때 같습니다. 참조 형식은 두 변수가 동일한 스토리지를 참조할 때 같습니다.

C# 언어 참조는 가장 최근에 릴리스된 C# 언어 버전을 문서화합니다. 또한 예정된 언어 릴리스의 공개 미리 보기 기능에 대한 초기 설명서도 포함되어 있습니다.

설명서는 언어의 마지막 세 버전 또는 현재 공개 미리 보기에서 처음 도입된 기능을 식별합니다.

팁 (조언)

C#에서 기능이 처음 도입된 시기를 찾으려면 C# 언어 버전 기록에 대한 문서를 참조하세요.

is== 대해 테스트할 때 테스트 대신 패턴 일치 연산자를 사용할 수 있습니다. 연산자는 is 모든 값 및 참조 형식에 대해 기본 같음 의미 체계를 사용합니다.

같음 연산자 ==

같음 연산자 ==는 피연산자가 같으면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

값 형식 같음

기본 제공 값 형식의 피연산자는 해당 값이 같은 경우 동일합니다.

int a = 1 + 2 + 3;
int b = 6;
Console.WriteLine(a == b);  // output: True

char c1 = 'a';
char c2 = 'A';
Console.WriteLine(c1 == c2);  // output: False
Console.WriteLine(c1 == char.ToLower(c2));  // output: True

참고

==산자가 <숫자(>또는<=)가 아니면 연산자의 경우 연산 결과는 다음과 같습니다.>= 이 조건은 값이 을 NaN 비롯한 double다른 float 값보다 크거나, 작거나NaN, 같지 않음을 의미합니다. 자세한 내용과 예제는 Double.NaN 또는 Single.NaN 참조 문서를 참조하세요.

기본 정수 형식의 해당 값이 같은 경우 동일한 열거형 형식의 피연산자가 동일합니다.

사용자 정의 구조체 형식은 기본적으로 == 연산자를 지원하지 않습니다. == 연산자를 지원하려면 사용자 정의 구조체가 해당 연산자를 오버로드해야 합니다.

C# 튜플은== 연산자를 != 기본적으로 지원합니다. 자세한 내용은 튜플 형식 문서의 튜플 같음 섹션을 참조하세요.

참조 형식 같음

기본적으로 레코드를 제외한 참조 형식 피연산자는 동일한 개체를 참조하는 경우 동일합니다.

public class ReferenceTypesEquality
{
    public class MyClass
    {
        private int id;

        public MyClass(int id) => this.id = id;
    }

    public static void Main()
    {
        var a = new MyClass(1);
        var b = new MyClass(1);
        var c = a;
        Console.WriteLine(a == b);  // output: False
        Console.WriteLine(a == c);  // output: True
    }
}

앞의 예제와 같이 사용자 정의 참조 형식은 기본적으로 연산자를 == 지원합니다. 그러나 참조 형식은 == 연산자를 오버로드할 수 있습니다. 참조 형식이 == 연산자를 오버로드하는 경우 Object.ReferenceEquals 메서드를 사용하여 해당 형식의 두 참조가 동일한 개체를 참조하는지 확인합니다.

레코드 형식 같음

레코드 형식은 기본적으로 값 같음 의미 체계를 제공하는 ==!= 연산자를 지원합니다. 즉, 두 레코드 피연산자는 둘 다 null 또는 모든 필드의 해당 값이고 자동으로 구현된 속성이 같을 때 동일합니다.

public class RecordTypesEquality
{
    public record Point(int X, int Y, string Name);
    public record TaggedNumber(int Number, List<string> Tags);

    public static void Main()
    {
        var p1 = new Point(2, 3, "A");
        var p2 = new Point(1, 3, "B");
        var p3 = new Point(2, 3, "A");

        Console.WriteLine(p1 == p2);  // output: False
        Console.WriteLine(p1 == p3);  // output: True

        var n1 = new TaggedNumber(2, new List<string>() { "A" });
        var n2 = new TaggedNumber(2, new List<string>() { "A" });
        Console.WriteLine(n1 == n2);  // output: False
    }
}

앞의 예제에서 알 수 있듯이 참조 형식 멤버의 같음은 특정 같음 구현을 사용하여 비교됩니다.

문자열 같음

두 개의 문자열 피연산자가 모두 null이거나 두 문자열 인스턴스가 같은 길이고 각 문자 위치에 동일한 문자가 있을 때 동일합니다.

string s1 = "hello!";
string s2 = "HeLLo!";
Console.WriteLine(s1 == s2.ToLower());  // output: True

string s3 = "Hello!";
Console.WriteLine(s1 == s3);  // output: False

문자열 같음 비교는 대/소문자를 구분하는 서수 비교입니다. 문자열 비교에 대한 자세한 내용은 C#에서 문자열을 비교하는 방법을 참조하세요.

대리자 같음

동일한 런타임 형식의 두 대리자 피연산자는 둘 다 null 이거나 호출 목록이 길이가 같고 각 위치에 동일한 항목이 있는 경우 동일합니다.

Action a = () => Console.WriteLine("a");

Action b = a + a;
Action c = a + a;
Console.WriteLine(object.ReferenceEquals(b, c));  // output: False
Console.WriteLine(b == c);  // output: True

중요합니다

호출 목록의 등가 항목에는 수신자를 포함하여 호출의 모든 고정 매개 변수가 포함됩니다. 수신기는 항목이 호출될 this 때 나타내는 개체의 인스턴스입니다.

var o1 = new object();
var o2 = new object();
var d1 = o1.ToString;
var d2 = o2.ToString;
Console.WriteLine(object.ReferenceEquals(d1, d2));  // output: False
Console.WriteLine(d1 == d2);  // output: False (different receivers)

자세한 내용은 C# 언어 사양대리자 같음 연산자 섹션을 참조하세요.

다음 예제와 같이 의미상 동일한 람다 식을 계산할 때 오는 대리자는 같지 않습니다.

Action a = () => Console.WriteLine("a");
Action b = () => Console.WriteLine("a");

Console.WriteLine(a == b);  // output: False
Console.WriteLine(a + b == a + b);  // output: True
Console.WriteLine(b + a == a + b);  // output: False

같지 않음 연산자 !=

같지 않음 연산 != 자는 피연산자가 같지 않으면 반환하고true, 그렇지 않으면 반환 false 합니다. 기본 제공 형식의 피연산자의 경우 식 x != y는 식 !(x == y)와 동일한 결과를 생성합니다. 형식 같음에 대한 자세한 내용은 같음 연산자 섹션을 참조하세요.

다음 예제에서는 연산자를 사용하는 != 방법을 보여 줍니다.

int a = 1 + 1 + 2 + 3;
int b = 6;
Console.WriteLine(a != b);  // output: True

string s1 = "Hello";
string s2 = "Hello";
Console.WriteLine(s1 != s2);  // output: False

object o1 = 1;
object o2 = 1;
Console.WriteLine(o1 != o2);  // output: True

클래스 계층 구조의 같음

레코드는 수동 작업 없이 상속을 올바르게 처리합니다. 컴파일러에서 생성된 같음은 런타임 형식과 선언된 모든 속성을 모두 확인하므로 대칭 및 전이성 요구 사항을 자동으로 충족합니다. 값 같음이 목표인 경우 수동 봉인되지 않은 계층 구조보다 선호 record 합니다.

중요합니다

가능할 때마다 사용합니다 record . 컴파일러는 필요한 모든 같음 멤버를 생성합니다. 수동 구현은 형식이 레코드가 아닌 클래스에서 파생되거나 이를 방지하는 record다른 제약 조건이 있는 경우에만 필요합니다.

형식이 레코드일 수 없는 경우 동등성을 직접 구현하세요

다음은 레코드가 될 수 없는 값 형식에 대한 최소 수동 구현입니다.

class Color : IEquatable<Color>
{
    public Color(int r, int g, int b)
    {
        R = r;
        G = g;
        B = b;
    }

    public int R { get; }
    public int G { get; }
    public int B { get; }

    public bool Equals(Color? other) =>
        other is not null && R == other.R && G == other.G && B == other.B;

    public override bool Equals(object? obj) => obj is Color other && Equals(other);
    public override int GetHashCode() => HashCode.Combine(R, G, B);
}

구현은 세 가지 Equals(T?) 필수 멤버를 제공합니다. 즉, 핵심 비교로 개체 override Equals(object?) 수준 호출 override GetHashCode() 에 대해 해시 기반 컬렉션이 올바르게 작동합니다. HashCode.Combine 는 .에서 사용하는 Equals것과 동일한 값으로 해시 하나를 빌드하는 라이브러리 도우미입니다. IEquatable<T> 구현(Equals(T?)오버로드)은 선택 사항이지만 호출자에게 구체적인 형식이 이미 있는 경우 boxing을 방지합니다.

또한 정의 == 하고 !=언어에 쌍으로 필요합니다. 경고 CS0660CS0661 은 네 멤버를 모두 일관성 있게 유지하도록 알려 줍니다.

위의 세 멤버가 있는 경우 Equals 값 같음을 반영하지만 == 아직 선언된 연산자가 없 == 으므로 ID를 테스트합니다.

var red1 = new Color(255, 0, 0);
var red2 = new Color(255, 0, 0);

Console.WriteLine(red1.Equals(red2)); // => True
Console.WriteLine(red1 == red2);      // => False  (no == overload; identity check)

올바른 구현은 동등 계약도 충족해야 합니다(예x: yz null이 아닌 경우).

  1. 반사: x.Equals(x) 반환합니다 true.
  2. 대칭: x.Equals(y) 와 동일한 값을 y.Equals(x)반환합니다.
  3. 전이적: 둘 다true인 경우 x.Equals(y)y.Equals(z) 다음 x.Equals(z) 이어야 true합니다.
  4. 일치: 두 개체가 모두 변경되지만 동일한 값을 반환하는 x.Equals(y) 연속 호출입니다.
  5. Null 동작: x.Equals(null) 반환 false; x.Equals(y) null x이 아닌 경우 호출할 때 throw하면 안됩니다.

봉인되지 않은 클래스 계층 구조의 값 같음은 대칭 및 전이적 규칙을 충족하기 위해 봉인된 클래스보다 더 많은 주의가 필요합니다. 디스패치는 런타임 형식이 IEquatable<T>.Equals(T? other) 아닌 변수의 선언된 형식 (변수 선언에 기록된 형식)을 따르는 것이 위험합니다. 비(non)Equals(Shape? other)virtual를 선언하는 경우 Shape 런타임에 있는 Circle 변수로 Shape 형식화된 변수는 자동으로 -specific 필드를 무시하여 Circle호출됩니다Shape.Equals. 변수를 통해 Shape 액세스할 때 반경이 다른 두 Circle 개체를 동일하게 비교할 수 있습니다.

올바른 패턴에는 두 가지 협조적 요구 사항이 필요합니다. 즉, 각 파생 클래스가 비교를 확장할 수 있도록 형식화된 Equals 메서드 virtual 를 만들고, 다른 런타임 형식의 개체가 같지 않도록 기본 클래스 구현에 가드를 추가 GetType() == other.GetType() 합니다.

기본 클래스 구현

// Shape is an unsealed base class. Making Equals virtual and guarding with GetType()
// ensures a derived instance is never equal to an instance of a different runtime type.
class Shape : IEquatable<Shape>
{
    public string Color { get; }
    public Shape(string color) => Color = color;

    public override bool Equals(object? obj) => Equals(obj as Shape);

    // virtual so derived classes can override and augment the comparison
    public virtual bool Equals(Shape? other) =>
        other is not null &&
        GetType() == other.GetType() &&   // reject different runtime types
        Color == other.Color;

    // GetType() is included because equality requires matching runtime types
    public override int GetHashCode() => HashCode.Combine(GetType(), Color);

    public static bool operator ==(Shape? l, Shape? r) => l?.Equals(r) ?? r is null;
    public static bool operator !=(Shape? l, Shape? r) => !(l == r);
}

핵심 사항:

  • virtual typed Equals: 각 파생 클래스는 자체 필드와의 비교를 보강하기 위해 이 메서드를 재정의합니다.
  • GetType() guard: GetType() == other.GetType()Circle 이 같은 개체가 같지 Shape 않도록 방지하고 다른 파생 형식의 개체가 서로 같지 않도록 합니다.
  • GetHashCode 에는 GetType()두 개체가 런타임 형식이 일치하는 GetHashCode 경우에만 같으므로 데이터 필드뿐만 아니라 런타임 형식을 해시해야 합니다. 여기서 생략하면 GetType() 잘못된 동작이 발생합니다.HashSet<T>Dictionary<TKey,TValue>
  • 대리자 : 연산자와 메서드 같음을 일관되게 유지합니다Equals.==

파생 클래스 구현

필드를 추가하는 파생 클래스는 형식화된 Equals형식을 재정의하고, 자체 형식으로 캐스팅하고, 호출 base.Equals한 다음, 고유한 필드를 비교합니다.

class Circle : Shape
{
    public double Radius { get; }
    public Circle(string color, double radius) : base(color) => Radius = radius;

    public override bool Equals(object? obj) => Equals(obj as Shape);

    // Calls base.Equals to verify Color and runtime type, then adds Radius
    public override bool Equals(Shape? other) =>
        other is Circle c && base.Equals(c) && Radius == c.Radius;

    public override int GetHashCode() => HashCode.Combine(GetType(), Color, Radius);
}

base.Equals(c) 는 가드를 GetType() 적용하고 공유 필드를 확인합니다. 인수 Shape 가 다른 파생 형식인 경우 캐스트를 통해 other is Circle c 빠르게 실패합니다.

기본 형식 변수를 통한 사용량

Shape circle1 = new Circle("red", 5.0);
Shape circle2 = new Circle("red", 7.0);
Shape circle3 = new Circle("red", 5.0);
Shape shape1  = new Shape("red");

Console.WriteLine(circle1.Equals(circle2)); // => False  (Radius differs)
Console.WriteLine(circle1.Equals(circle3)); // => True
Console.WriteLine(circle1.Equals(shape1));  // => False  (different runtime types)

봉인된 클래스는 더 간단합니다.

클래스를 서브클래스 sealed 할 수 없으므로 컴파일 시간 및 런타임 형식은 항상 동의합니다. 경비원이나 virtual 디스패치가 필요하지 GetType() 않습니다. 형식이 IEquatable<T>레코드가 될 수 없는 경우 같음 구현에 표시된 패턴이 올바르고 봉인된 클래스에 대해 완료됩니다.

연산자 오버로드 가능성

사용자 정의 형식의 연산자와 == 연산자를 !=할 수 있습니다. 이러한 두 연산자 중 하나를 오버로드하는 경우 다른 연산자도 오버로드해야 합니다.

레코드 형식의 연산자와 == 연산자를 != 명시적으로 오버로드할 수 없습니다. 레코드 형식==에 대한 연산자 !=T 연산자의 동작을 변경하려면 다음 시그니처를 IEquatable<T>.Equals 사용하여 메서드를 구현합니다.

public virtual bool Equals(T? other);

C# 언어 사양

자세한 내용은 C# 언어 사양관계형 및 형식 테스트 연산자 섹션을 참조하세요.

레코드 형식의 같음에 대한 자세한 내용은 C# 언어 사양같음 멤버 섹션을 참조하세요.

참고 항목