using System; using System.Collections.Generic; using System.Collections; using System.Linq; using System.Text; using System.Reflection; namespace ExpressionsParser { /// /// Generic "by value" comparer that uses IComparable and can compare arrays/lists. /// public class ValueComparer : IComparer, IComparer, IValueComparer { internal readonly static ValueComparer _Instance = new ValueComparer(); public static IValueComparer Instance { get { return _Instance; } } /// /// Gets or sets format provider used for Convert.ChangeType (InvariantCulture by default). /// public IFormatProvider FormatProvider { get; set; } /// /// Determines how ValueComparer handles comparison exceptions (by default is false: convert exceptions are thrown). /// public bool SuppressErrors { get; set; } /// /// Determines how ValueComparer handles comparison with nulls (default is "MinValue" mode). /// public NullComparisonMode NullComparison { get; set; } public ValueComparer() { FormatProvider = System.Globalization.CultureInfo.InvariantCulture; SuppressErrors = false; } private bool IsAssignableFrom(Type a, Type b) { #if NET40 return a.IsAssignableFrom(b); #else return a.GetTypeInfo().IsAssignableFrom(b.GetTypeInfo() ); #endif } int IComparer.Compare(object a, object b) { var res = CompareInternal(a, b); if (!res.HasValue) throw new ArgumentException(string.Format("Cannot compare {0} and {1}", a.GetType(), b.GetType())); return res.Value; } int IComparer.Compare(object a, object b) { return ((IComparer)this).Compare(a, b); } public int? Compare(object a, object b) { try { if (NullComparison == NullComparisonMode.Sql) if (a == null || b == null) return null; return CompareInternal(a, b); } catch { if (SuppressErrors) return null; throw; } } protected virtual int? CompareInternal(object a, object b) { if (a == null && b == null) return 0; if (a == null && b != null) return -1; if (a != null && b == null) return 1; if ((a is IList) && (b is IList)) { IList aList = (IList)a; IList bList = (IList)b; if (aList.Count < bList.Count) return -1; if (aList.Count > bList.Count) return +1; for (int i = 0; i < aList.Count; i++) { int? r = Compare(aList[i], bList[i]); if (!r.HasValue || r != 0) return r; } // lists are equal return 0; } // test for quick compare if a type is assignable from b if (a is IComparable) { var aComp = (IComparable)a; // quick compare if types are fully compatible if (IsAssignableFrom( a.GetType(), b.GetType() )) return aComp.CompareTo(b); } if (b is IComparable) { var bComp = (IComparable)b; // quick compare if types are fully compatible if (IsAssignableFrom( b.GetType(), a.GetType() )) return -bComp.CompareTo(a); } // try to convert b to a and then compare if (a is IComparable) { var aComp = (IComparable)a; var bConverted = Convert.ChangeType(b, a.GetType(), FormatProvider); return aComp.CompareTo(bConverted); } // try to convert a to b and then compare if (b is IComparable) { var bComp = (IComparable)b; var aConverted = Convert.ChangeType(a, b.GetType(), FormatProvider); return -bComp.CompareTo(aConverted); } return null; } public enum NullComparisonMode { /// /// Null compared as "MinValue" (affects less-than and greater-than comparisons). /// /// This is default behaviour expected for and described in MSDN. MinValue = 0, /// /// Null cannot be compared to any other value (even if it is null). This is SQL-like nulls handling. /// Sql = 1, } } }