Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得
本節提出如何利用 IComparable<T> 和 IComparer<T> 寫出高效的物件比較功能(e.g. <, >, <=, >=),其主要應用於物件排序時提供比較的演算法。
範例程式碼:
1. 實作 IComparable<T> 與 IComparable 比較物件大小。
public struct Customer : IComparable<Customer>, IComparable
{
public string Name { get; }
public Customer( string name )
{
Name = name;
}
public int CompareTo( Customer other ) =>
Name.CompareTo( other.Name );
int IComparable.CompareTo( object obj )
{
try
{
var customer = ( Customer ) obj;
return CompareTo( customer );
}
catch ( InvalidCastException )
{
throw;
}
}
}
在實作 IComparable<T> 時也需實作 IComparable,原因在於較舊的版本(在 .NET 2.0 以前)並沒有泛型的版本可供使用;其可能產生裝箱、拆箱操作。然而藉由明確指定 CompareTo 的泛型介面實作方法為 public 可以避免錯誤呼叫到非泛型的版本。
Customer c1;
Employee e1;
// No longer compiles.
if( c1.CompareTo( e1 ) > 0 )
Console.WriteLine( "Customer one is greater" );
反之需要明確轉型為 IComparable 才能通過編譯。
Customer c1;
Employee e1;
// Explicit casts to IComparable.
// Compiles successful.
if( ( ( IComparable )c1 ).CompareTo( e1 ) > 0 )
Console.WriteLine( "Customer one is greater" );
2. 實作 <, >, <=, >= 增加介面實作完整性。
public struct Customer : IComparable<Customer>, IComparable
{
public string Name { get; }
public Customer( string name )
{
Name = name;
}
public int CompareTo( Customer other ) =>
Name.CompareTo( other.Name );
int IComparable.CompareTo( object obj )
{
try
{
var customer = ( Customer ) obj;
return CompareTo( customer );
}
catch ( InvalidCastException )
{
throw;
}
}
public static bool operator <( Customer left, Customer right ) =>
left.CompareTo( right ) < 0;
public static bool operator <=( Customer left, Customer right ) =>
left.CompareTo( right ) <= 0;
public static bool operator >( Customer left, Customer right ) =>
left.CompareTo( right ) > 0;
public static bool operator >=( Customer left, Customer right ) =>
left.CompareTo( right ) >= 0;
}
Note:<, >, <=, >= 運算子和 Equals 與 == 並不衝突,排序時的比較和物件本身是否相等可以是分開的邏輯。(比如說同樣為 18 歲男性,使用年齡排序 CompareTo 會回傳 0;但不同人代表著不同個體,Equals 和 == 會回傳 false。)
3. 實作 Comparsion<T> 委派與 IComparer<T> 擴充不同的比較方式。
public struct Customer : IComparable<Customer>, IComparable
{
public static Comparison<Customer> CompareByRevenue =>
( left, right ) => left.Revenue.CompareTo( right.Revenue );
public static IComparer<Customer> RevComparer =>
_revComp.Value;
private static Lazy<RevenueComparer> _revComp =>
new Lazy<RevenueComparer>( ( ) => new RevenueComparer( ) );
public string Name { get; }
private double Revenue { get; set; }
public Customer( string name, double revenue )
{
Name = name;
Revenue = revenue;
}
public int CompareTo( Customer other ) =>
Name.CompareTo( other.Name );
int IComparable.CompareTo( object obj )
{
try
{
var customer = ( Customer ) obj;
return CompareTo( customer );
}
catch ( InvalidCastException )
{
throw;
}
}
public static bool operator <( Customer left, Customer right ) =>
left.CompareTo( right ) < 0;
public static bool operator <=( Customer left, Customer right ) =>
left.CompareTo( right ) <= 0;
public static bool operator >( Customer left, Customer right ) =>
left.CompareTo( right ) > 0;
public static bool operator >=( Customer left, Customer right ) =>
left.CompareTo( right ) >= 0;
// Class to compare customers by revenue.
// This is always used via the interface pointer,
// so only provide the interface override.
private class RevenueComparer : IComparer<Customer>
{
int IComparer<Customer>.Compare( Customer left, Customer right ) =>
left.Revenue.CompareTo( right.Revenue );
}
}
Customer 現在也可以依照 Revenue 來排序。
結論:
1. 當實作了 IComparable<T>,也應同時實作 <, >, <=, >= 以保持結果一致。
2. IComparable<T> 比 IComparable 少了裝箱、拆箱操作。
3. 實作 Comparsion<T> 委派或 IComparer<T> 擴充不同的比較方式。
1. 當實作了 IComparable<T>,也應同時實作 <, >, <=, >= 以保持結果一致。
2. IComparable<T> 比 IComparable 少了裝箱、拆箱操作。
3. 實作 Comparsion<T> 委派或 IComparer<T> 擴充不同的比較方式。