Skip to content

Instantly share code, notes, and snippets.

@rdumont
Created October 29, 2014 03:29
Show Gist options
  • Select an option

  • Save rdumont/d0392668185337ae707a to your computer and use it in GitHub Desktop.

Select an option

Save rdumont/d0392668185337ae707a to your computer and use it in GitHub Desktop.

Revisions

  1. rdumont created this gist Oct 29, 2014.
    63 changes: 63 additions & 0 deletions SettingsValueEqualityComparer.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,63 @@
    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();
    }
    }
    62 changes: 62 additions & 0 deletions SettingsValueEqualityComparerTests.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,62 @@
    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);
    }
    }