关于将自定义比较器与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)); } }更多推荐
发布评论