为含列表的可序列化StreamResourceUsage实现CompareTo的最优方案
StreamResourceUsage类相等性与比较逻辑的最优实现方案
核心需求回顾
需实现StreamResourceUsage类的IEquatable<StreamResourceUsage>与IComparable<StreamResourceUsage>接口,要求:
- 仅当两个对象的
AccountName相等,且StreamInformation列表的元素**完全一致(不考虑顺序)**时,判定两个对象相等; CompareTo方法不得修改原对象的列表状态,避免多线程环境下的问题;- 解决外部通过公开DataMember修改列表的风险,保证对象状态一致性;
最优方案:基于不可变集合(ImmutableList)实现
优先选择使用System.Collections.Immutable.ImmutableList<T>替代List<T>,这是最简洁且符合Service Fabric可靠集合特性的方案,具体实现如下:
1. 先确保StreamInfo实现相等与比较接口
列表元素的相等性与排序依赖于StreamInfo自身的比较逻辑:
[DataContract] public class StreamInfo : IEquatable<StreamInfo>, IComparable<StreamInfo> { [DataMember] public string StreamId { get; set; } [DataMember] public long Usage { get; set; } public bool Equals(StreamInfo other) { if (other is null) return false; return string.Equals(StreamId, other.StreamId, StringComparison.Ordinal) && Usage == other.Usage; } public override bool Equals(object obj) => Equals(obj as StreamInfo); public override int GetHashCode() { var hash = new HashCode(); hash.Add(StreamId, StringComparer.Ordinal); hash.Add(Usage); return hash.ToHashCode(); } public int CompareTo(StreamInfo other) { if (other is null) return 1; var idCompare = string.Compare(StreamId, other.StreamId, StringComparison.Ordinal); if (idCompare != 0) return idCompare; return Usage.CompareTo(other.Usage); } }
2. StreamResourceUsage类的实现
using System.Collections.Immutable; using System.Runtime.Serialization; [DataContract] public class StreamResourceUsage : IEquatable<StreamResourceUsage>, IComparable<StreamResourceUsage> { [DataMember] public string AccountName { get; set; } [DataMember] public ImmutableList<StreamInfo> StreamInformation { get; set; } = ImmutableList<StreamInfo>.Empty; // IEquatable实现 public bool Equals(StreamResourceUsage other) { if (other is null) return false; if (ReferenceEquals(this, other)) return true; // 先比较AccountName if (!string.Equals(AccountName, other.AccountName, StringComparison.Ordinal)) return false; // 直接判断元素集合是否完全相等(无需排序) return StreamInformation.SetEquals(other.StreamInformation); } public override bool Equals(object obj) => Equals(obj as StreamResourceUsage); public override int GetHashCode() { var hash = new HashCode(); hash.Add(AccountName, StringComparer.Ordinal); // 无序组合元素哈希,避免顺序影响哈希值 foreach (var info in StreamInformation) hash.Add(info); return hash.ToHashCode(); } // IComparable实现 public int CompareTo(StreamResourceUsage other) { if (other is null) return 1; if (ReferenceEquals(this, other)) return 0; // 优先比较AccountName var accountCompare = string.Compare(AccountName, other.AccountName, StringComparison.Ordinal); if (accountCompare != 0) return accountCompare; // 创建排序后的临时不可变列表,不修改原对象状态 var sortedThis = StreamInformation.OrderBy(info => info).ToImmutableList(); var sortedOther = other.StreamInformation.OrderBy(info => info).ToImmutableList(); // 逐元素比较 var minCount = Math.Min(sortedThis.Count, sortedOther.Count); for (int i = 0; i < minCount; i++) { var elementCompare = sortedThis[i].CompareTo(sortedOther[i]); if (elementCompare != 0) return elementCompare; } // 元素一致时,比较列表长度 return sortedThis.Count.CompareTo(sortedOther.Count); } // 重载运算符,完善相等性逻辑 public static bool operator ==(StreamResourceUsage left, StreamResourceUsage right) => Equals(left, right); public static bool operator !=(StreamResourceUsage left, StreamResourceUsage right) => !Equals(left, right); public static bool operator <(StreamResourceUsage left, StreamResourceUsage right) => left?.CompareTo(right) < 0; public static bool operator >(StreamResourceUsage left, StreamResourceUsage right) => left?.CompareTo(right) > 0; }
方案优势
- 彻底解决外部修改问题:
ImmutableList<T>是只读集合,外部无法修改元素、添加/删除元素或排序,保证对象状态的一致性; - 安全的比较逻辑:
CompareTo中通过OrderBy生成新的排序集合,完全不修改原对象的状态,符合比较方法的设计规范; - 高效的相等性判断:使用
SetEquals直接验证元素集合的相等性,无需排序,性能优于先排序再逐元素比较; - 适配Service Fabric场景:可靠集合对不可变对象的支持更好,并发访问时无修改冲突,适合持久化存储。
对两种思路的补充分析
思路1:临时HashSet+ImmutableList
仅用HashSet判断相等性可行,但无法直接用于CompareTo的排序比较;结合ImmutableList后,就成为了上述的最优方案,既解决了外部修改问题,也能实现有序比较。
思路2:封装私有列表+深拷贝暴露
- 优点:可继续使用
List<T>; - 缺点:每次访问公开属性都要深拷贝列表,性能开销大;若
StreamInfo是可变类型,外部仍可修改拷贝中的元素(虽不影响内部,但违背不可变设计原则);序列化配置更繁琐,需要额外处理私有字段的DataMember标注。
内容的提问来源于stack exchange,提问作者Saher Ahwal
相关产品推荐
相关产品推荐

