Created
October 29, 2014 03:29
-
-
Save rdumont/d0392668185337ae707a to your computer and use it in GitHub Desktop.
Comparing Two Objects: How Hard Can It Be?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
public class SettingsValueEqualityComparer : IEqualityComparer<object> | |
{ | |
public new bool Equals(object a, object b) | |
{ | |
// If the equality operator considers them to be equal, great! | |
// Both being null should fall in this case. | |
if (a == b) | |
return true; | |
// Now we know that at least one of them isn't null. | |
// If the other is null, then the two are different. | |
if (a == null || b == null) | |
return false; | |
// Let's see if they are both numbers. In that case, they should | |
// be compared as decimals, which fits any of the other number types. | |
var na = IsNumeric(a); | |
var nb = IsNumeric(b); | |
if (na && nb) | |
return Convert.ToDecimal(a) == Convert.ToDecimal(b); | |
// We found that at least one of them isn't a number. | |
// If the other is, then the two are different. | |
if (na || nb) | |
return false; | |
// Our last resort is to check if one of them is IComparable. | |
// If it is and the other has the same type, then they can be compared. | |
var ca = a as IComparable; | |
if (ca != null && a.GetType() == b.GetType()) | |
return ca.CompareTo(b) == 0; | |
// Anything else should be considered different. | |
return false; | |
} | |
private static bool IsNumeric(object value) | |
{ | |
// Yes, 11 types. And they cannot be directly compared using the IComparable | |
// interface because an exception is thrown when different numeric types are used. | |
return value is sbyte | |
|| value is byte | |
|| value is short | |
|| value is ushort | |
|| value is int | |
|| value is uint | |
|| value is long | |
|| value is ulong | |
|| value is float | |
|| value is double | |
|| value is decimal; | |
} | |
public int GetHashCode(object obj) | |
{ | |
// I don't need this method, so here is a simple implementation just because | |
return obj == null ? 0 : obj.GetHashCode(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Newtonsoft.Json.Linq; | |
using NUnit.Framework; | |
[TestFixture] | |
public class SettingsValueEqualityComparerTests | |
{ | |
private readonly object[][] successCases = | |
{ | |
new object[] {"abc", "abc"}, | |
new object[] {123, 123}, | |
new object[] {1.0, 1}, | |
new object[] {123, 123L}, | |
new object[] {123L, 123}, | |
new object[] {new DateTime(2014, 1, 1), new DateTime(2014, 1, 1)}, | |
// It is important to know that values parsed from JSON are treated correctly | |
new object[] {JToken.Parse("123"), JToken.Parse("123")}, | |
new object[] {null, null}, | |
}; | |
[TestCaseSource("successCases")] | |
public void Success(object a, object b) | |
{ | |
// Arrange | |
var comparer = new SettingsValueEqualityComparer(); | |
// Act & Assert | |
Assert.That(comparer.Equals(a, b), Is.True); | |
} | |
private readonly object[][] failureCases = | |
{ | |
new object[] {"abc", "def"}, | |
new object[] {"abc", 123}, | |
new object[] {123, "abc"}, | |
new object[] {123, "123"}, | |
new object[] {123, 456}, | |
new object[] {123L, 456}, | |
new object[] {123, 456L}, | |
new object[] {new DateTime(2014, 1, 1), new DateTime(2000, 12, 31)}, | |
new object[] {"abc", new DateTime(2000, 12, 31)}, | |
new object[] {new DateTime(2014, 1, 1), "abc"}, | |
new object[] {JToken.Parse("123"), JToken.Parse("456")}, | |
// arrays and objects should never be considered equal, even if they seem to be | |
new object[] {JToken.Parse("[]"), JToken.Parse("[]")}, | |
new object[] {JToken.Parse("{}"), JToken.Parse("{}")}, | |
new object[] {JToken.Parse("{}"), JToken.Parse("[]")}, | |
new object[] {new {a = "b"}, new {a = "b"}}, | |
new object[] {"abc", null}, | |
new object[] {null, "abc"}, | |
}; | |
[TestCaseSource("failureCases")] | |
public void Failure(object a, object b) | |
{ | |
// Arrange | |
var comparer = new SettingsValueEqualityComparer(); | |
// Act & Assert | |
Assert.That(comparer.Equals(a, b), Is.False); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment