Skip to content

Instantly share code, notes, and snippets.

@rdumont
Created October 29, 2014 03:29
Show Gist options
  • Save rdumont/d0392668185337ae707a to your computer and use it in GitHub Desktop.
Save rdumont/d0392668185337ae707a to your computer and use it in GitHub Desktop.
Comparing Two Objects: How Hard Can It Be?
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();
}
}
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