C# : struct와 class에서의 Eqaulity
C#에서 struct는 Stack에 저장하는 Value Type이고, class는 Heap에 저장하는 Reference Type이다.
Value Type과 Reference Type은 메모리의 위치 뿐만 아니라, 값을 비교하는 방식에도 차이를 보인다. Value Type인 struct는 두 인스턴스를 비교할 때 Equals() 메서드를 사용하는데, 두 인스턴스의 Type이 같고 그 값이 같다면 True를 리턴한다. Value Type을 비교할 때는 == Operator를 사용할 수 없다.
Reference Type인 class의 경우, 두 인스턴스의 Type이 같고 그 값이 같은 지를 체크하는 Equals() 메서드와 두 변수가 동일한 메모리를 가리키는 지 체크하는 == Operator를 사용한다. (class의 경우 (IEquatable of T).Equals() 인터페이스를 구현하여 인스턴스의 데이타가 같은 지를 체크하도록 한다. 만약 class가 IEquatable 인터페이스를 구현하지 않으면, (실제 데이타가 같더라도) Equals()는 False를 리턴한다)
Value Type과 Reference Type은 메모리의 위치 뿐만 아니라, 값을 비교하는 방식에도 차이를 보인다. Value Type인 struct는 두 인스턴스를 비교할 때 Equals() 메서드를 사용하는데, 두 인스턴스의 Type이 같고 그 값이 같다면 True를 리턴한다. Value Type을 비교할 때는 == Operator를 사용할 수 없다.
Reference Type인 class의 경우, 두 인스턴스의 Type이 같고 그 값이 같은 지를 체크하는 Equals() 메서드와 두 변수가 동일한 메모리를 가리키는 지 체크하는 == Operator를 사용한다. (class의 경우 (IEquatable of T).Equals() 인터페이스를 구현하여 인스턴스의 데이타가 같은 지를 체크하도록 한다. 만약 class가 IEquatable 인터페이스를 구현하지 않으면, (실제 데이타가 같더라도) Equals()는 False를 리턴한다)
예제
// (1) 두 MyData 구조체의 값이 같으므로 Equals()는 True를 리턴함
public struct MyData
{
public int Category { get; set; }
public String Name { get; set; }
}
MyData m = new MyData();
m.Category = 1;
m.Name = "Watch";
MyData m2 = new MyData();
m2.Category = 1;
m2.Name = "Watch";
bool same = m.Equals(m2); // True
// (2) MyData를 class로 바꾸고, IEquatable을 구현하면 m.Equals(m2)는 True를 리턴함.
// IEquatable을 구현하지 않으면 m.Equals(m2)는 False를 리턴함.
public class MyData : IEquatable<MyData>
{
public int Category { get; set; }
public String Name { get; set; }
public bool Equals(MyData other) {
return this.Category == other.Category && this.Name == other.Name;
}
}
// (3) 레퍼런스가 동일한 경우 Equals() 혹은 ==은 True를 리턴함
var m2 = m;
bool same = m.Equals(m2); // True
same = m == m2; // True
public struct MyData
{
public int Category { get; set; }
public String Name { get; set; }
}
MyData m = new MyData();
m.Category = 1;
m.Name = "Watch";
MyData m2 = new MyData();
m2.Category = 1;
m2.Name = "Watch";
bool same = m.Equals(m2); // True
// (2) MyData를 class로 바꾸고, IEquatable을 구현하면 m.Equals(m2)는 True를 리턴함.
// IEquatable을 구현하지 않으면 m.Equals(m2)는 False를 리턴함.
public class MyData : IEquatable<MyData>
{
public int Category { get; set; }
public String Name { get; set; }
public bool Equals(MyData other) {
return this.Category == other.Category && this.Name == other.Name;
}
}
// (3) 레퍼런스가 동일한 경우 Equals() 혹은 ==은 True를 리턴함
var m2 = m;
bool same = m.Equals(m2); // True
same = m == m2; // True
C# : record class
C# 9에서 도입된 record 타입은 record class 타입으로도 불리우는데, class 레퍼런스 타입의 속성을 가지면서 필드값이 변하지 않는 Immuntable한 속성을 함께 갖는 타입이다. record class 타입은 Equals() 혹은 == Operator을 사용하여 두 인스턴스를 마치 Value Type인 것처럼 비교하게 된다. 즉, record 타입은 두 인스턴스가 같은 타입이고 그 필드 값들이 동일하면, Equals() 혹은 == Operator에서 True를 리턴한다.
예제
public record MyData
{
public int Category { get; init; }
public String Name { get; init; }
}
class Program
{
static void Main(string[] args)
{
MyData m1 = new MyData { Category = 1, Name = "Watch" };
MyData m2 = new MyData { Category = 1, Name = "Watch" };
bool same1 = m1.Equals(m2); // true
bool same2 = m1 == m2; // true
}
}
{
public int Category { get; init; }
public String Name { get; init; }
}
class Program
{
static void Main(string[] args)
{
MyData m1 = new MyData { Category = 1, Name = "Watch" };
MyData m2 = new MyData { Category = 1, Name = "Watch" };
bool same1 = m1.Equals(m2); // true
bool same2 = m1 == m2; // true
}
}
C# 10 : record struct
C# 10에서는 record struct 타입을 새로 도입하였는데, C# 9의 'record'를 C# 10에서는 'record' 혹은 'record class'로 정의할 수 있다. record class는 record 레퍼런스 타입이고, record struct는 record Value 타입이다. C# 9의 record 타입은 record class 타입인데, C# 9에서는 'record class'라고 쓸 수 없고, C# 10에서는 새로 도입된 record struct와 명시적으로 분리하기 위해, record class라는 키워드를 도입하였다.
struct 타입에서 동일성(Equality)을 체크하기 위해서는 Equals()를 사용하여 두 인스턴스의 값들이 같은 지를 체크하는데, record struct는 (record class와 마찬가지로) Equals()와 == Operator를 함께 사용할 수 있다. record class와 같이, record struct도 두 인스턴스의 필드값이 같으면 True를 리턴한다.
struct 타입에서 동일성(Equality)을 체크하기 위해서는 Equals()를 사용하여 두 인스턴스의 값들이 같은 지를 체크하는데, record struct는 (record class와 마찬가지로) Equals()와 == Operator를 함께 사용할 수 있다. record class와 같이, record struct도 두 인스턴스의 필드값이 같으면 True를 리턴한다.
예제
public record struct MyData
{
public int Category { get; init; }
public int Id { get; set; }
}
class Program
{
static void Main(string[] args)
{
MyData m1 = new MyData { Category = 1, Id = 100 };
MyData m2 = new MyData { Category = 1, Id = 100 };
bool same1 = m1.Equals(m2); // 두 값이 같으므로 true
m1.Id = 200;
bool diff = m1 == m2; // Id가 변경되었으므로 false
}
}
{
public int Category { get; init; }
public int Id { get; set; }
}
class Program
{
static void Main(string[] args)
{
MyData m1 = new MyData { Category = 1, Id = 100 };
MyData m2 = new MyData { Category = 1, Id = 100 };
bool same1 = m1.Equals(m2); // 두 값이 같으므로 true
m1.Id = 200;
bool diff = m1 == m2; // Id가 변경되었으므로 false
}
}
C# 10 : recode 타입에서 sealed ToString() 사용
C# 10에서는 record class 타입에서 ToString() 메서드를 정의할 때, sealed 키워드를 사용할 수 있게 되었다. sealed를 쓰는 효과는 해당 record class의 파생 타입에서 새로 ToString()을 정의하지 못하게 하고, 고정된 ToString() 메서드를 사용하도록 한다.
예제
var d = new MyDerived();
Console.WriteLine(d.ToString());
record class MyBase
{
public int Id { get; set; } = 0;
// C# 10: sealed 사용 가능
public sealed override string ToString()
{
return $"{Id}";
}
}
record class MyDerived : MyBase
{
public int Category { get; set; }
}
Console.WriteLine(d.ToString());
record class MyBase
{
public int Id { get; set; } = 0;
// C# 10: sealed 사용 가능
public sealed override string ToString()
{
return $"{Id}";
}
}
record class MyDerived : MyBase
{
public int Category { get; set; }
}