Code/Helpers/Comparison.cs
using System;
using System.Collections.Generic;
namespace Expressive.Helpers
{
/// <summary>
/// Helper methods for use when comparing types/values.
/// </summary>
public static class Comparison
{
private static readonly Type[] CommonTypes =
{
typeof(DateTime), // If it can be interpreted as a DateTime use that.
typeof(decimal), // Decimal is stored as 96 bits of value, plus a sign, plus an exponent
typeof(double), // Double is stored as a 64 bit floating point
typeof(long), // 64 bit signed integer
typeof(int), // 32 bit signed integer
typeof(bool), // Process booleans before strings
typeof(string), // If it's not anything else, it can be a string.
};
/// <summary>
/// Performs a comparison of two objects by trying to find the most suitable type and returns
/// a value indicating whether one object is less than, equal to, or greater than the other.
/// </summary>
/// <param name="lhs">The first object to compare.</param>
/// <param name="rhs">The second object to compare.</param>
/// <param name="context">Any additional <see cref="Context"/> that may effect the comparion
/// (e.g. string case sensitivity, etc.).</param>
/// <returns>A signed integer that indicates the relative values of <paramref name="lhs"/>
/// and <paramref name="rhs"/>, as shown in the following table.Value Meaning Less than zero
/// <paramref name="lhs"/> is less than <paramref name="rhs"/>.Zero <paramref name="lhs"/>
/// equals <paramref name="rhs"/>. Greater than zero <paramref name="lhs"/> is greater than
/// <paramref name="rhs"/>.</returns>
public static int CompareUsingMostPreciseType(object lhs, object rhs, Context context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var mostPreciseType = GetMostPreciseType(lhs?.GetType(), rhs?.GetType());
if (mostPreciseType == typeof(string))
{
return string.Compare(
(string)Convert.ChangeType(lhs, mostPreciseType, context.CurrentCulture),
(string)Convert.ChangeType(rhs, mostPreciseType, context.CurrentCulture),
context.EqualityStringComparison);
}
return Comparison.Compare(lhs, rhs, mostPreciseType, context);
}
private static Type GetMostPreciseType(Type a, Type b)
{
if (a == b)
{
return a; // If they're the same type, just return one of them.
}
foreach (var t in Comparison.CommonTypes)
{
if (a == t || b == t)
{
return t;
}
}
return a;
}
private static int Compare(object lhs, object rhs, Type mostPreciseType, Context context)
{
// If at least one is null then the check is simple.
if (lhs is null && rhs is null)
{
return 0;
}
if (lhs is null)
{
return -1;
}
if (rhs is null)
{
return 1;
}
var lhsType = lhs.GetType();
var rhsType = rhs.GetType();
if (lhsType == rhsType)
{
return Comparer<object>.Default.Compare(lhs, rhs);
}
// Attempt to convert using the mostPreciseType first.
try
{
if (lhsType == mostPreciseType)
{
rhs = Convert.ChangeType(rhs, mostPreciseType, context.CurrentCulture);
}
else
{
lhs = Convert.ChangeType(lhs, mostPreciseType, context.CurrentCulture);
}
return Comparer<object>.Default.Compare(lhs, rhs);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
}
// Attempt to convert the RHS to match the LHS.
try
{
return Comparer<object>.Default.Compare(lhs, Convert.ChangeType(rhs, lhsType, context.CurrentCulture));
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
}
// Attempt to convert the LHS to match the RHS.
try
{
return Comparer<object>.Default.Compare(lhs, Convert.ChangeType(rhs, lhsType, context.CurrentCulture));
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
}
// Failing that resort to a string.
try
{
return string.Compare(
(string)Convert.ChangeType(lhs, typeof(string), context.CurrentCulture),
(string)Convert.ChangeType(rhs, typeof(string), context.CurrentCulture),
context.EqualityStringComparison);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
}
return 0;
}
}
}