IComparable / IComparable<T>
선후 관계 / 대소 비교 등을 위한 인터페이스를 제공한다.
어떤 클래스의 인스턴스로 이루어진 컬렉션의 정렬을 위해 사용된다.
public interface IComparable<in T>
{
int CompareTo([AllowNull] T other);
}
CompareTo : 해당 메서드는 int 형을 반환하는데 다음의 선후 관계를 의미한다.
- 0보다 작음 : 해당 인스턴스(Caller)가 other 보다 선의 관계를 가지며 정렬 시 other보다 앞에 배치된다.
- 0 : other와 동등한 관계이다.
- 0보다 큼 : 해당 인스턴스가 other 보다 후의 관계를 가지며 정렬 시 other보다 뒤에 배치된다.
예제
public class Temperature : IComparable
{
protected double temperatureF;
public int CompareTo(object obj)
{
if (obj == null)
return 1;
Temperature otherTemperature = obj as Temperature;
if (otherTemperature != null)
return this.temperatureF.CompareTo(otherTemperature.temperatureF);
else
throw new ArgumentException("Object is not a Temperature");
}
public double Fahrenheit { get { return this.temperatureF; } set { this.temperatureF = value; } }
public double Celcius { get { return (this.temperatureF - 32) * (5.0 / 9); } set { this.temperatureF = (value * 9.0 / 5) + 32; } }
}
IComparable<T>를 구현할 경우 int CompareTo(T other)를 구현해야한다.
제너릭 버전의 IComparable<T>를 구현하는 경우 해당 CompareTo 활용도가 높으므로 연산자를 오버로딩 하는 것이 좋다.
public class Temperature : IComparable<Temperature>
{
public int CompareTo(Temperature other)
{
// If other is not a valid object reference, this instance is greater.
if (other == null) return 1;
// The temperature comparison depends on the comparison of
// the underlying Double values.
return m_value.CompareTo(other.m_value);
}
public static bool operator >(Temperature operand1, Temperature operand2)
{
return operand1.CompareTo(operand2) > 0;
}
public static bool operator <(Temperature operand1, Temperature operand2)
{
return operand1.CompareTo(operand2) < 0;
}
public static bool operator >=(Temperature operand1, Temperature operand2)
{
return operand1.CompareTo(operand2) >= 0;
}
public static bool operator <=(Temperature operand1, Temperature operand2)
{
return operand1.CompareTo(operand2) <= 0;
}
protected double m_value = 0.0;
public double Celsius
{
get
{
return m_value - 273.15;
}
}
public double Kelvin
{
get
{
return m_value;
}
set
{
if (value < 0.0)
{
throw new ArgumentException("Temperature cannot be less than absolute zero.");
}
else
{
m_value = value;
}
}
}
public Temperature(double kelvins)
{
this.Kelvin = kelvins;
}
}
Comparer 인터페이스
Comparer, Comparer<T>: Compare 비교 연산을 하는 클래스에 대한 인터페이스
public interface IComparer<in T>
{
int Compare([AllowNull] T x, [AllowNull] T y);
}
두 인자를 비교하여 선 후 관계를 반환하여야한다. 어떤 클래스의 비교를 IComparer를 상속받은 클래스의 인스턴스를 이용하여 위임할 수 있을 것이다.
public class TempComparer : IComparer<Temperature>
{
public int Compare(Temperature left, Temperature right)
{
return left.Celsius.CompareTo(right.Celsius);
}
}
// IComparable<Temperature>를 구현한 Temperature 클래스 안에서
static private TempComparer Comp = new TempComparer();
public int CompareTo(Temperature other)
{
if (other == null) return 1;
return Comp.Compare(this, other);
}
Comparison 대리자
Comparer<T>의 Compare 메서드와 같은 시그니쳐를 갖는 대리자 형식이 Comparison<T>로 정의되어있다.
public delegate int Comparison<in T>(T x, T y);
대리자를 이용하여 CompareTo를 구현한다.
static private Comparison<Temperature> CompDelegate =
(Temperature left, Temperature right) => { return left.Celsius.CompareTo(right.Celsius); };
public int CompareTo(Temperature other)
{
if (other == null) return 1;
return CompDelegate(this, other);
}
IEquatable<T>
두 인스턴스가 동등한 지 판단하는 bool Equals(T other)를 구현해야한다.
public interface IEquatable<T>
{
bool Equals([AllowNull] T other);
}
IEquatable<T> 인터페이스는 제너릭 컬렉션에서 자주 사용되는데 (Dictionary<TKey, TValue>, List<T>, LinkedList<T>), Contains, IndexOf, LastIndexOf, Remove등 에서 두 요소가 같은지 판별하기 위해 해당 인터페이스가 사용된다.
IComparable<T>와 차이점
IComparable<T>의 경우 원소를 정렬하기 위해 사용되는 인터페이스 이며 int를 반환하고 IEqutable은 두 인스턴스가 동등한 지 판별하기 때문에 bool를 반환한다.
IEqutable<T>를 구현한다면, T 타입 클래스가 반드시 Object.Equals(Object)와 Object.GetHashCode()를 오버라이드하여 행동이 Equals<T>와 동일하게 동작하도록 해야한다.
T가 Value-Type 인 경우
bool Equals(T) / bool Equals(Object) / int GetHashCode()를 정의 혹은 재정의해주고 operator ==와 != 연산자 오버로딩을 하여 Equals와 같은 방식으로 동작하게 해준다.
public struct Data : IEquatable<Data>
{
public DateTime Date { get; set; }
public int Value { get; set; }
public static bool operator==(Data lhs, Data rhs)
{
return lhs.Equals(rhs);
}
public static bool operator!=(Data lhs, Data rhs)
{
return !(lhs == rhs);
}
public bool Equals(Data rhs)
{
return Date.Year == rhs.Date.Year && Date.Month == rhs.Date.Month && Date.Day == rhs.Date.Day
&& Value == rhs.Value;
}
public override bool Equals(object rhs)
{
if (rhs == null || !(rhs is Data))
return false;
return Equals((Data)rhs);
}
public override int GetHashCode()
{
return Date.GetHashCode() ^ Value.GetHashCode();
}
}
T가 Reference-Type 인 경우
bool Equals(T) / bool Equals(Object) / int GetHashCode()를 정의 혹은 재정의해주나 연산자 오버로딩은 하지 않는다.
==가 보통 참조의 같음을 비교하는 데 사용하고 내부적인 값의 같음으로 사용하는 것이 드물기 때문이다.
public class Product : IEquatable<Product>
{
public int Index { get; set; }
public string Tag { get; set; }
public override bool Equals(Object other)
{
return Equals(other as Product);
}
public override int GetHashCode()
{
return Index.GetHashCode() ^ Tag.GetHashCode();
}
public bool Equals([AllowNull] Product other)
{
if (Object.ReferenceEquals(this, other))
return true;
if (Object.ReferenceEquals(null, other))
return false;
// Product 파생 클래스가 있다면 정확한 타입 비교 필요
// if (this.GetType() != other.GetType())
// return false;
return Index == other.Index && Tag == other.Tag;
}
}
'C# > C# 인터페이스' 카테고리의 다른 글
C# 인터페이스 vs 추상 클래스 (0) | 2022.04.09 |
---|---|
C# 공변성과 반공변성 Covariance / Contravariance (0) | 2021.11.20 |
ICollection : 컬렉션 인터페이스 (0) | 2021.11.13 |
IEnumerable / IEnumerator : 순회 가능한 컬렉션과 순회하는 방법 (0) | 2021.11.13 |
IDisposable : 비관리 리소스 해제하기 (0) | 2021.11.12 |