C#中HashSet<List<float>>调用SetEquals返回false但预期为true
这个问题的核心原因其实很简单——HashSet<T>默认用的是引用相等来判断List<float>这类引用类型的元素是否相同,而不是比较它们的内容!
你用HashSet<int>没问题,是因为int是值类型,默认的相等比较就是比较值本身;但List<float>是引用类型,哪怕两个List的内容完全一样,只要它们是不同的对象实例,EqualityComparer<List<float>>.Default就会认为它们不相等,这就导致SetEquals返回false了。
接下来给你两种实用的解决方案:
方案一:自定义相等比较器(推荐,改动最小)
我们可以写一个实现IEqualityComparer<List<float>>的类,让HashSet用这个比较器来判断List的内容是否相等,而不是看引用。
先写比较器类:
public class ListFloatEqualityComparer : IEqualityComparer<List<float>> { public bool Equals(List<float> x, List<float> y) { // 处理null的边界情况 if (x == null && y == null) return true; if (x == null || y == null) return false; // 长度不同直接不相等 if (x.Count != y.Count) return false; // 逐个比较元素,这里要注意float的精度问题: // 如果你的数值是精确的(比如字面量赋值),直接用==就行; // 如果是计算得到的,建议用阈值判断(比如Math.Abs(x[i]-y[i]) < 1e-6) for (int i = 0; i < x.Count; i++) { if (x[i] != y[i]) return false; } return true; } public int GetHashCode(List<float> obj) { if (obj == null) return 0; int hash = 17; // 结合每个元素的哈希码生成List的哈希值 foreach (float f in obj) { hash = hash * 31 + f.GetHashCode(); } return hash; } }
然后在创建HashSet的时候,把这个比较器传进去:
// 初始化HashSet时指定自定义比较器 var thisList = new HashSet<List<float>>(new ListFloatEqualityComparer()); var otherList = new HashSet<List<float>>(new ListFloatEqualityComparer());
这样之后,thisList.SetEquals(otherList)就会正确识别内容相同的List为相等元素,返回true了。
注意:
SetEquals没有重载可以单独指定比较器,所以必须在HashSet创建时就传入比较器——因为HashSet内部的哈希表是基于创建时的比较器构建的。
方案二:改用自定义值类型(更优雅,适合长期使用)
如果你不想每次创建HashSet都传比较器,可以把List<float>换成一个自定义的不可变值类型,重写Equals和GetHashCode来实现内容比较:
public readonly struct FloatList : IEquatable<FloatList> { private readonly List<float> _items; public FloatList(IEnumerable<float> items) { _items = new List<float>(items); } public bool Equals(FloatList other) { if (_items.Count != other._items.Count) return false; for (int i = 0; i < _items.Count; i++) { if (_items[i] != other._items[i]) return false; } return true; } public override bool Equals(object obj) { return obj is FloatList other && Equals(other); } public override int GetHashCode() { int hash = 17; foreach (float f in _items) { hash = hash * 31 + f.GetHashCode(); } return hash; } public static bool operator ==(FloatList left, FloatList right) { return left.Equals(right); } public static bool operator !=(FloatList left, FloatList right) { return !left.Equals(right); } }
之后用HashSet<FloatList>来存储数据,默认的相等比较器就会按内容判断相等了,不需要额外传比较器:
var thisList = new HashSet<FloatList>(); thisList.Add(new FloatList(new List<float> {10.0f, 15.0f})); // 同理初始化otherList...
补充:关于float精度的小提示
如果你的float数值是通过计算得到的(不是直接字面量赋值),直接用==比较可能会因为精度误差导致误判,这时候可以把Equals方法里的比较改成:
if (Math.Abs(x[i] - y[i]) > 1e-6) // 阈值可以根据你的业务需求调整 return false;
这样就能避免微小的精度差异导致的错误判断啦。
内容的提问来源于stack exchange,提问作者Alex Tereshenkov

