做法 20

以 IComparable<T> 與 IComparer<T> 實作排序關係

實作

public struct Customer : IComparable<Customer>, IComparable
{
    private readonly string name;
    private double revenue;

    public Customer(string name)
    {
        this.name = name;
    }

    public int CompareTo(Customer other)
    {
        return name.CompareTo(other.name);
    }

    int IComparable.CompareTo(object? obj)
    {
        if (obj is Customer other)
        {
            return this.CompareTo(other);
        }
        throw new ArgumentException("Argument must be a Customer", nameof(obj));
    }

    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;

    private static Lazy<RevenueComparer> revComp = new Lazy<RevenueComparer>(() => new());

    public static IComparer<Customer> RevenueCompare => revComp.Value;
    public static Comparison<Customer> CompareByRevenue => (left, right) => left.revenue.CompareTo(right.revenue);

    private class RevenueComparer : IComparer<Customer>
    {
        public int Compare(Customer left, Customer right)
        {
            return left.revenue.CompareTo(right.revenue);
        }
    }
}

使用方式

static void Main(string[] args)
{
    // 創建一些 Customer 實例
    var customers = new List<Customer>
    {
    new Customer("Alice",1000) ,
    new Customer("Bob",1500) ,
    new Customer("Charlie",1200),
    new Customer("David",800)
    };

    // 1. 使用默認比較(按名稱)
    customers.Sort();
    Console.WriteLine("Sorted by name:");
    // Alice: 1000
    // Bob: 1500
    // Charlie: 1200
    // David: 800
    PrintCustomers(customers);

    // 2. 使用運算符比較
    var alice = new Customer("Alice");
    var bob = new Customer("Bob");
    // Alice < Bob: True
    // Alice <= Bob: True
    // Alice > Bob: False
    // Alice >= Bob: False
    Console.WriteLine($"Alice < Bob: {alice < bob}");
    Console.WriteLine($"Alice <= Bob: {alice <= bob}");
    Console.WriteLine($"Alice > Bob: {alice > bob}");
    Console.WriteLine($"Alice >= Bob: {alice >= bob}");

    // 3. 使用 RevenueCompare
    customers.Sort(Customer.RevenueCompare);
    Console.WriteLine("\nSorted by revenue (using RevenueCompare):");
    // David: 800
    // Alice: 1000
    // Charlie: 1200
    // Bob: 1500
    PrintCustomers(customers);

    // 4. 使用 CompareByRevenue
    customers.Sort(Customer.CompareByRevenue);
    Console.WriteLine("\nSorted by revenue (using CompareByRevenue):");
    // David: 800
    // Alice: 1000
    // Charlie: 1200
    // Bob: 1500
    PrintCustomers(customers);

    // 5. 使用 LINQ 排序
    var sortedByName = customers.OrderBy(c => c);
    Console.WriteLine("\nSorted by name (using LINQ):");
    // lice: 1000
    // Bob: 1500
    // Charlie: 1200
    // David: 800

    PrintCustomers(sortedByName);

    var sortedByRevenue = customers.OrderBy(c => c, Customer.RevenueCompare);
    Console.WriteLine("\nSorted by revenue (using LINQ and RevenueCompare):");
    // David: 800
    // Alice: 1000
    // Charlie: 1200
    // Bob: 1500
    PrintCustomers(sortedByRevenue);
}

static void PrintCustomers(IEnumerable<Customer> customers)
{
    foreach (var customer in customers)
    {
        Console.WriteLine($"{customer.ToString()}");
    }
}

做法 21

建構支援 Disposable 型別參數的泛型類別

如果你建構的泛型類別的型別參數描述的任何型別實例,你必須考慮到這些型別可能有實作 IDisposable。你必須防衛性的撰寫程式並確保這些物件離開範圍後不會洩漏資源。

public interface IEngine
{
    void DoWork();
}

private class Engine : IEngine
{
    public void DoWork()
    {
        Console.WriteLine("Hello World!");
    }
}

public class EngineDriver1<T> where T : IEngine, new()
{
    public void GetThingsDone()
    {
        T engine = new T();
        using (engine as IDisposable)
        {
            engine.DoWork();
        }
    }
}

// using (var x = new EngineDriver2<Engine>())
// {
//     x.GetThingsDone();
// }
public class EngineDriver2<T> : IDisposable where T : IEngine, new()
{
    private Lazy<T> engine = new Lazy<T>(() => new T());

    public void GetThingsDone()
    {
        using (engine.Value as IDisposable)
        {
            engine.Value.DoWork();
        }
    }

    public void Dispose()
    {
        if (engine.IsValueCreated)
        {
            var resource = engine.Value as IDisposable;
            resource?.Dispose();
        }
    }
}

public class EngineDriver3<T> where T : IEngine
{
    private T engine;

    public EngineDriver3(T engine)
    {
        this.engine = engine;
    }

    public void GetThingsDone()
    {
        engine.DoWork();
    }
}

若 T 沒有實作 IDisposable ,則此區域變數的值為 null。

如果 T(在這個例子中是 Engine)沒有實作 IDisposable 接口,那麼 engine as IDisposable 的結果就會是 null。 這個 null 值並不會導致異常或錯誤。 using 語句被設計為可以安全地處理 null 值。當 disposable 為 null 時, using 語句簡單地不執行任何釋放資源的操作。 這就是為什麼即使 Engine 類沒有實作 IDisposable,代碼仍然可以正常運行。 using 語句在這種情況下實際上不會做任何特殊的事情,代碼會繼續執行 engine.DoWork()。