C# IComparable
IComparable是C#和.NET中确认对象之间相对顺序的标准协议之一。
准确的来说IComparable包括IComparable和IComparable<T>两个接口。
而另外一种则是我们运用的非常之多的“<”和“>”运算符。
IComparablede的定义方式如下:
public interface IComparable { int CompareTo(object othor); } public interface IComparable<in T> { int CompareTo(T othor); }
这两个接口实现了相同的功能。对于值类型,泛型安全的接口执行速度比非泛型要快。
它们的CompareTo方法按照如下的方式执行。
- 如果a在b之后,则a.CompareTo(b)返回一个正数
- 如果a和b的位置相同,a.CompareTo(b)返回0
- 如果a在b之前,a.CompareTo(b)返回一个负数
Console.WriteLine("A".CompareTo("B"));//-1 Console.WriteLine("B".CompareTo("B"));//0 Console.WriteLine("B".CompareTo("A"));//1
上面这个例子可以看出来String类型是有实现IComparable接口的,那不然怎么能使用这个方法。
那当我们自己在创建一个结构体想要提升它的比较效率的时候,我们自己也能通过IComparable来实现。
上代码
public struct Note : IComparable<Note>, IEquatable<Note>, IComparable { int _semitonesFromA; public int SemitonesFromA { get => _semitonesFromA; } public Note(int semitonesFromA) { _semitonesFromA = semitonesFromA; } /// <summary> /// 实现泛型接口 /// </summary> /// <param name="other"></param> /// <returns></returns> public int CompareTo(Note other) { if (Equals(other)) return 0; return _semitonesFromA.CompareTo(other._semitonesFromA); } /// <summary> /// 实现非泛型接口 /// </summary> /// <param name="obj"></param> /// <returns></returns> int IComparable.CompareTo(object obj) { if (!(obj is Note)) throw new InvalidOperationException("不是Note"); return CompareTo((Note)obj); } /*重载“<”和“>”运算符*/ public static bool operator >(Note n1, Note n2) => n1.CompareTo(n2) > 0; public static bool operator <(Note n1, Note n2) => n1.CompareTo(n2) < 0; /// <summary> /// 实现IEquatable /// </summary> /// <param name="other"></param> /// <returns></returns> public bool Equals(Note other) => other._semitonesFromA == this._semitonesFromA; /// <summary> /// 重写Equals /// </summary> /// <param name="obj"></param> /// <returns></returns> public override bool Equals(object obj) { if (!(obj is Note)) return false; return Equals((Note)obj); } /// <summary> /// 重写GetHashCode /// </summary> /// <returns></returns> public override int GetHashCode() { return this._semitonesFromA.GetHashCode(); } /*重载==和!=运算符*/ public static bool operator ==(Note n1, Note n2) => n1.Equals(n1); public static bool operator !=(Note n1, Note n2) => !n1.Equals(n1); }
测试
var note = new Note(9); var note2 = new Note(10); var note3 = new Note(11); Console.WriteLine(note2.CompareTo(note3));//-1 Console.WriteLine(note2.CompareTo(note2));//0 Console.WriteLine(note3.CompareTo(note));//1 Console.WriteLine(note3 > note2);//true Console.WriteLine(note2 < note);//false
上面这个例子重载了IComparable接口并且重载了运算符“<”和“>”。同时还实现了IEquatable接口,详情见我博客相等比较。
这里就说说为啥要重载运算符“<”和“>”。
运算符“<”和“>”的是实现在功能上和IComparable接口时一致的。当然这是整个.NET Framework的标准做法。
我们的例子重载了运算符“<”和“>”,但实际情况下实现IComparable并不一定需要实现运算符“<”和“>”,但是如果重载运算符“<”和“>”那么就必须要实现IComparable。
对于这个必须,我的理解是运算符“<”和“>”的实现是必须要基于IComparable的。
那实现IComparable时,我们什么样的情况下,需要实现运算符“<”和“>”呢。
- 类型具有固有的“大于”和“小于”的概念(对于IComparable的更宽泛的“之前”和“之后”)
- 这种比较只能用一种方式或在一个上下文下执行。
- 比较的结果在各文化中保持不变。
所有的数值类型基本都实现了运算符“<”和“>”,因为数值类型都符合这三个条件。
System.String就不满足最后一点bool ok="Back">"Anne"这句代码就会报错。