HashSet.Contains和自定义IEqualityComparer的意外结果(Unexpected results with HashSet.Contains and custom IEqua

编程入门 行业动态 更新时间:2024-10-27 11:24:05
HashSet.Contains和自定义IEqualityComparer的意外结果(Unexpected results with HashSet.Contains and custom IEqualityComparer)

关于将自定义比较器与HashSet一起使用,我必须有一些误解。 我收集了很多不同类型的数据,我作为Json中间存储。 为了对它进行操作,我使用的是Json.NET,特别是JObject , JArray和Jtoken 。

通常我会在收集时为这些东西添加一些内联元素,并以“tbp_”为前缀。 我需要知道在(或不是)之前是否收集了表示为JObject的特定数据。 为了做到这一点,我有一个自定义的IEqualityComparer ,它扩展了Json.NET提供的实现。 它在使用提供的实现检查值相等之前去掉元数据:

public class EntryComparer : JTokenEqualityComparer { private static string _excludedPrefix = "tbp_"; public JObject CloneForComparison(JObject obj) { var clone = obj.DeepClone() as JObject; var propertiesToRemove = clone .Properties() .Where(p => p.Name.StartsWith(_excludedPrefix)); foreach (var property in propertiesToRemove) { property.Remove(); } return clone; } public bool Equals(JObject obj1, JObject obj2) { return base.Equals(CloneForComparison(obj1), CloneForComparison(obj2)); } public int GetHashCode(JObject obj) { return base.GetHashCode(CloneForComparison(obj)); } }

我使用HashSet来跟踪我正在操作的数据,因为我只需要知道它是否已经存在。 我使用EntryComparer的实例初始化HashSet。 我的测试是:

public class EntryComparerTests { EntryComparer comparer; JObject j1; JObject j2; public EntryComparerTests() { comparer = new EntryComparer(); j1 = JObject.Parse(@" { 'tbp_entry_date': '2017-03-25T21:25:53.127993-04:00', 'from_date': '1/6/2017', 'to_date': '2/7/2017', 'use': '324320', 'reading': 'act', 'kvars': '0.00', 'demand': '699.10', 'bill_amt': '$28,750.75' }"); j2 = JObject.Parse(@" { 'tbp_entry_date': '2017-03-10T18:59:00.537745-05:00', 'from_date': '1/6/2017', 'to_date': '2/7/2017', 'use': '324320', 'reading': 'act', 'kvars': '0.00', 'demand': '699.10', 'bill_amt': '$28,750.75' }"); } [Fact] public void Test_Equality_Comparer_GetHashCode() { Assert.Equal(comparer.GetHashCode(j1), comparer.GetHashCode(j2)); Assert.Equal(true, comparer.Equals(j1, j2)); } [Fact] public void Test_Equality_Comparer_Hashset_Contains() { var hs = new HashSet<JObject>(comparer); hs.Add(j1); Assert.Equal(true, hs.Contains(j2)); } }

Test_Equality_Comparer_GetHashCode()传递,但Test_Equality_Comparer_Hashset_Contains()失败。 j1和j2应该被视为相等并且是根据第一次测试的结果,所以我在这里缺少什么?

I must have some sort of misunderstanding with respect to using a custom comparer with a HashSet. I collect a lot of different types of data that I store intermediately as Json. In order to operate on it I am using Json.NET, specifically JObject, JArray, and Jtoken.

Generally I add a bit of metadata inline to this stuff at collection time, and it is prefixed with "tbp_". I need to know if a particular bit of data, represented as a JObject, has been collected before (or not). In order to do so I have a custom IEqualityComparer that extends the implementation provided by Json.NET. It strips out the metadata before checking for value equality using the provided implementation:

public class EntryComparer : JTokenEqualityComparer { private static string _excludedPrefix = "tbp_"; public JObject CloneForComparison(JObject obj) { var clone = obj.DeepClone() as JObject; var propertiesToRemove = clone .Properties() .Where(p => p.Name.StartsWith(_excludedPrefix)); foreach (var property in propertiesToRemove) { property.Remove(); } return clone; } public bool Equals(JObject obj1, JObject obj2) { return base.Equals(CloneForComparison(obj1), CloneForComparison(obj2)); } public int GetHashCode(JObject obj) { return base.GetHashCode(CloneForComparison(obj)); } }

I use a HashSet to track the data I'm operating on, since I just need to know if it already exists or not. I initialize the HashSet with an instance of EntryComparer. My tests are:

public class EntryComparerTests { EntryComparer comparer; JObject j1; JObject j2; public EntryComparerTests() { comparer = new EntryComparer(); j1 = JObject.Parse(@" { 'tbp_entry_date': '2017-03-25T21:25:53.127993-04:00', 'from_date': '1/6/2017', 'to_date': '2/7/2017', 'use': '324320', 'reading': 'act', 'kvars': '0.00', 'demand': '699.10', 'bill_amt': '$28,750.75' }"); j2 = JObject.Parse(@" { 'tbp_entry_date': '2017-03-10T18:59:00.537745-05:00', 'from_date': '1/6/2017', 'to_date': '2/7/2017', 'use': '324320', 'reading': 'act', 'kvars': '0.00', 'demand': '699.10', 'bill_amt': '$28,750.75' }"); } [Fact] public void Test_Equality_Comparer_GetHashCode() { Assert.Equal(comparer.GetHashCode(j1), comparer.GetHashCode(j2)); Assert.Equal(true, comparer.Equals(j1, j2)); } [Fact] public void Test_Equality_Comparer_Hashset_Contains() { var hs = new HashSet<JObject>(comparer); hs.Add(j1); Assert.Equal(true, hs.Contains(j2)); } }

Test_Equality_Comparer_GetHashCode() passes, but Test_Equality_Comparer_Hashset_Contains() fails. j1 and j2 should be treated as equal and are according to the results of the first test, so what am I missing here?

最满意答案

更改班级的签名:

public class EntryComparer : JTokenEqualityComparer, IEqualityComparer<JObject>

否则使用的GetHashCode()和Equals()是基类中的那些(具有不同的“签名”......基类实现IEqualityComparer<JToken> ,因此不调用您的方法通过HashSet<> )。

然后有一个小错误删除属性:

var propertiesToRemove = clone .Properties() .Where(p => p.Name.StartsWith(_excludedPrefix)) .ToArray();

更好的方法是“隐藏” JTokenEqualityComparer并使其成为私有字段,如:

public class EntryComparer : IEqualityComparer<JObject> { private static readonly JTokenEqualityComparer _comparer = new JTokenEqualityComparer(); private static readonly string _excludedPrefix = "tbp_"; public static JObject CloneForComparison(JObject obj) { var clone = obj.DeepClone() as JObject; var propertiesToRemove = clone .Properties() .Where(p => p.Name.StartsWith(_excludedPrefix)) .ToArray(); foreach (var property in propertiesToRemove) { property.Remove(); } return clone; } public bool Equals(JObject obj1, JObject obj2) { return _comparer.Equals(CloneForComparison(obj1), CloneForComparison(obj2)); } public int GetHashCode(JObject obj) { return _comparer.GetHashCode(CloneForComparison(obj)); } }

Change the signature of the class:

public class EntryComparer : JTokenEqualityComparer, IEqualityComparer<JObject>

otherwise the GetHashCode() and the Equals() that are used are the ones in the base class (that has a different "signature"... The base class implements the IEqualityComparer<JToken>, for this reason your methods aren't called by HashSet<>).

Then there is a small bug for the properties removal:

var propertiesToRemove = clone .Properties() .Where(p => p.Name.StartsWith(_excludedPrefix)) .ToArray();

Better would be to "hide" the JTokenEqualityComparer and make it a private field, like:

public class EntryComparer : IEqualityComparer<JObject> { private static readonly JTokenEqualityComparer _comparer = new JTokenEqualityComparer(); private static readonly string _excludedPrefix = "tbp_"; public static JObject CloneForComparison(JObject obj) { var clone = obj.DeepClone() as JObject; var propertiesToRemove = clone .Properties() .Where(p => p.Name.StartsWith(_excludedPrefix)) .ToArray(); foreach (var property in propertiesToRemove) { property.Remove(); } return clone; } public bool Equals(JObject obj1, JObject obj2) { return _comparer.Equals(CloneForComparison(obj1), CloneForComparison(obj2)); } public int GetHashCode(JObject obj) { return _comparer.GetHashCode(CloneForComparison(obj)); } }

更多推荐

本文发布于:2023-08-03 01:24:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1382441.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:自定义   意外   IEqualityComparer   HashSet   results

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!