C# 基础 IComparable<in T> 与 IComparer <in T>内容详解、使用场景、示例及区别

1、 IComparer < in T >

概述

IComparer<in T> 接口位于 System.Collections.Generic 命名空间中,用于定义比较两个对象的方法。

它通常用于自定义排序顺序。

接口定义

public interface IComparer<in T>
{
    int Compare(T x, T y);
}
  • T: 要比较的对象的类型。IComparer<in T> 是逆变的,这意味着可以使用指定类型或其基类。

方法int Compare(T x, T y);

int Compare(T x, T y);
  • 参数:
    - x: 第一个要比较的对象。
    - y: 第二个要比较的对象。
  • 返回值:
    - 小于 0:x 小于 y
    - 等于 0:x 等于 y
    - 大于 0:x 大于 y

使用场景

IComparer<T> 接口通常与以下方法一起使用,根据不同的比较器实现排序和查找

  • List<T>.Sort
  • List<T>.BinarySearch
  • Array.Sort
  • Array.BinarySearch

相关类

  • Comparer<T>: IComparer<T> 的默认实现。
  • StringComparer: 为 String 类型实现 IComparer<T> 接口。

注意事项

  • 建议从 Comparer<T> 类派生,而不是直接实现 IComparer<T> 接口,因为 Comparer<T> 类提供了默认实现和其他有用的方法。

示例

以下示例展示了如何实现 IComparer<T> 接口来提供多种对标题进行排序的比较器。

  • 按照标题序号进行排序

    /// <summary>
    /// 按照标题的序号进行排序
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class TitleComparerByVersion<T> : Comparer<String>
    {
        /// <summary>
        /// 比较两个字符串表示的序号大小。
        /// </summary>
        /// <param name="x">第一个版本号字符串</param>
        /// <param name="y">第二个版本号字符串</param>
        /// <returns>如果 x 大于 y,返回 1;如果 x 小于 y,返回 -1;相等则返回 0</returns>
        public override int Compare(string? x, string? y)
        {
            if (string.IsNullOrEmpty(x) && string.IsNullOrEmpty(y))
            {
                return 0;
            }
            if (string.IsNullOrEmpty(x))
            {
                return -1;
            }
            if (string.IsNullOrEmpty(y))
            {
                return 1;
            }

            var xOrderStr = x.Split(' ')[0];
            var yOrderStr = y.Split(' ')[0];

            var xNumList = xOrderStr.Split('.');
            var yNumList = yOrderStr.Split('.');

            int length = Math.Min(xNumList.Length, yNumList.Length);

            for (int i = 0; i < length; i++)
            {
                if (int.TryParse(xNumList[i], out int xNum) && int.TryParse(yNumList[i], out int yNum))
                {
                    if (xNum != yNum)
                    {
                        return xNum.CompareTo(yNum);
                    }
                }
                else
                {
                    throw new ArgumentException("版本号格式不正确");
                }
            }

            return xNumList.Length.CompareTo(yNumList.Length);
        }
    }


 [TestMethod]
 public void DemoSort()
 {
     var titles = new List<string>
     {
         "1.1.3",
         "1.1.1",
         "1.1.2.1",
         "1.1.2",
     };
     
     titles.Sort(_comparer);
     var expected = new List<string>
     {
         "1.1.1",
         "1.1.2",
         "1.1.2.1",
         "1.1.3",
     };
     Assert.IsTrue(expected.SequenceEqual(titles));
 }
  • 按照标题字母顺序进行排序

    /// <summary>
    /// 按照标题的字母顺序进行排序
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class TitleCompareByAlphabet<T> : Comparer<string>
    {
        /// <summary>
        /// 按照字母顺序进行排序
        /// </summary>
        /// <param name="x">第一个字符串</param>
        /// <param name="y">第二个字符串</param>
        /// <returns>比较结果</returns>
        public override int Compare(string? x, string? y)
        {
            if (string.IsNullOrEmpty(x) && string.IsNullOrEmpty(y))
            {
                return 0;
            }
            if (string.IsNullOrEmpty(x))
            {
                return -1;
            }
            if (string.IsNullOrEmpty(y))
            {
                return 1;
            }

            int minLength = Math.Min(x.Length, y.Length);

            for (int i = 0; i < minLength; i++)
            {
                int res = x[i].CompareTo(y[i]);
                if (res != 0)
                {
                    return res;
                }
            }

            return x.Length.CompareTo(y.Length);
        }
    }


        [TestMethod]
        public void DemoSort()
        { 
            var titles = new List<string> { "apple", "banana", "apricot", "apples", "appliqué" };
            titles.Sort(_titleComparer);
            var expected = new List<string> { "apple", "apples", "appliqué", "apricot", "banana" };
            Assert.IsTrue(titles.SequenceEqual(expected));
        }

参考

(1) IComparer 接口 (System.Collections.Generic) | Microsoft … https://learn.microsoft.com/zh-cn/dotnet/api/system.collections.generic.icomparer-1?view=net-8.0.
(2) IComparer Interface (System.Collections) | Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/api/system.collections.icomparer?view=net-8.0.
(3) C# lambda表达式和IComparer-腾讯云开发者社区-腾讯云. https://cloud.tencent.com/developer/ask/sof/93306.
(4) C#泛型-泛型接口IComparer——比较器 - 流星落 - 博客园. https://www.cnblogs.com/tanding/archive/2012/06/29/2569392.html.
(5) C# IComparer 详解:如何实现和使用它-CSDN博客. https://blog.csdn.net/xiefeng240601/article/details/139375277.

2、 IComparable < in T >

概述

IComparable 接口定义了一种通用的比较方法CompareTo(T other).

用于创建特定类型的比较方法,以对其实例进行排序,实现此接口的类型可以与同一类型的其他对象进行比较。

接口定义

public interface IComparable<in T>
{
    int CompareTo(T other);
}

方法CompareTo(T other)

  • 描述:比较与当前对象同一类型的另一个对象,并返回一个整数,指示当前对象在排序顺序中的相对位置。
  • 返回值
    - 小于零:当前对象小于 other
    - 等于零:当前对象等于 other
    - 大于零:当前对象大于 other

使用场景

  • 排序:实现 IComparable<in T> 接口的对象可以使用 Array.SortList<T>.Sort 等方法进行排序。
  • 集合操作:在 SortedList<TKey, TValue>SortedDictionary<TKey, TValue> 等集合中使用时,可以根据键进行排序。

注意事项

  • 一致性:实现 CompareTo 方法时,应确保比较操作的一致性和对称性。

一致性要求 CompareTo 方法在相同的对象之间进行比较时,始终返回相同的结果。这意味着,如果你在不同的时间或不同的环境中比较相同的两个对象,结果应该是一样的。

对称性要求 CompareTo 方法的返回值在两个对象之间是互为相反数的。也就是说,如果 x.CompareTo(y) 返回一个正数,那么 y.CompareTo(x) 应该返回一个负数,反之亦然。

  • 相等性:当实现 IComparable<in T> 接口时,通常还需要重写 EqualsGetHashCode 方法,以确保对象的相等性逻辑与比较逻辑一致。

示例

以下实现了一个Apple类,它实现了 IComparable接口,用于比较两个Apple对象的排序顺序。
在这个例子中,我们根据苹果的大小(Size)和糖分(Sugar)的比例进行比较,其中大小占70%权重,糖分占30%权重。


    public class Apple : IComparable<Apple>
    {
        public string Variety { get; set; }
        public int Size { get; set; }
        public int Sugar { get; set; }
        public string Guid { get; set; }

        public int CompareTo(Apple other)
        {
            if (Size * 0.7 + Sugar * 0.3 > other.Size * 0.7 + other.Sugar * 0.3)
                return 1;
            else if (Size * 0.7 + Sugar * 0.3 < other.Size * 0.7 + other.Sugar * 0.3)
                return -1;
            else
                return 0;
        }

        public override bool Equals(object? obj)
        {
            var orther = obj as Apple;
            if (orther == null)
                return false;
            return Size * 0.7 + Sugar * 0.3 == orther.Size * 0.7 + orther.Sugar * 0.3;
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(Guid);
        }
    }
    
 
[TestMethod]
public void SortDemo()
{
    List<Apple> apples = new List<Apple>
    {
        new Apple { Size = 10, Sugar = 5 },
        new Apple { Size = 8, Sugar = 6 },
        new Apple { Size = 12, Sugar = 4 }
    };

    apples.Sort();

    Assert.AreEqual(8, apples[0].Size);
    Assert.AreEqual(10, apples[1].Size);
    Assert.AreEqual(12, apples[2].Size);

}

3、 IComparer和IComparable区别

IComparerIComparable 是 .NET 框架中用于定义对象比较逻辑的两种不同方式,它们在实现位置****、灵活性以及****用途等方面有所区别,具体分析如下:

  1. 实现位置
    • IComparable<T> 在类内部实现。
    • IComparer<T> 在类外部实现。
  2. 灵活性
    • IComparable<T> 只能定义一种排序标准。
    • IComparer<T> 可以定义多种排序标准。
    • IComparable<T> 一旦类定义了比较逻辑,后续更改可能需要对类本身进行修改。而 IComparer<T> 允许在不修改原有类的情况下添加新的比较逻辑,这有利于维护和扩展现有代码。因此,IComparer<T> 具有更高的版本兼容能力。
  3. 用途
    • IComparable<T> 用于定义对象的自然排序。
    • IComparer<T> 用于自定义排序逻辑。

针对上述分析,提出以下几点建议:

  • 如果希望对象的比较逻辑能够灵活地在不同的上下文环境中变化,建议使用 IComparer<T>
  • 如果您的应用中对象自然地知道如何比较自己,并且不需要多种比较策略,那么应实现 IComparable<T>
  • 对于经常一起使用的一组类,您可能会发现同时实现这两个接口是最有用的,因为这样既提供了默认的比较逻辑,又保持了一定的灵活性。

在中文环境中,可能容易混淆这两个接口。其实只要理解其词性即可。IComparable 中的 able 代表能力,表示对象拥有此能力,而 Comparer 代表比较器,是名词,可以使用不同的比较器实现不同的比较结果。

参考资料

(1) c# - difference between IComparable and IComparer - Stack Overflow. https://stackoverflow.com/questions/5980780/difference-between-icomparable-and-icomparer.

(2) When to use IComparable Vs. IComparer - Stack Overflow. https://stackoverflow.com/questions/538096/when-to-use-icomparablet-vs-icomparert.

(3) IComparable, IComparer And IEquatable Interfaces In C#. https://www.c-sharpcorner.com/UploadFile/80ae1e/icomparable-icomparer-and-iequatable-interfaces-in-C-Sharp/.

(4) C# 中的IComparable和IComparer - HueiFeng - 博客园. https://www.cnblogs.com/yyfh/p/12129000.html.

(5) C# IComparable接口、IComparer接口以及排序应用(超详细 … https://blog.csdn.net/weixin_42185134/article/details/103149334.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值