private static bool Compare(object obj1, object obj2, ObjectReferenceDictionary recursiveObjects)
        {
            bool equals;

            if ((obj1 == null) || (obj2 == null))
            {
                equals = ((obj1 == null) && (obj2 == null));
                if (!equals)
                {
                    var type = (obj1 ?? obj2).GetType();
                    LogFailedComparison("at least one of the instances is null", type,
                                        $"obj1 is{(obj1 == null ? "" : "n't")} null", $"obj2 is{(obj2 == null ? "" : "n't")} null");
                }
            }
            else
            {
                IFieldValueEqualityComparer comparer;
                if (MatchesComparer(obj1, obj2, out comparer) || AreObjectsSameType(obj1, obj2) || AreObjectsDictionaries(obj1, obj2) || AreObjectsEnumerables(obj1, obj2))
                {
                    var dictionary1 = obj1 as IDictionary;
                    if (comparer != null)
                    {
                        equals = comparer.Equals(obj1, obj2);
                    }
                    else if (obj1 is byte[])
                    {
                        var lhs = (byte[])obj1;
                        var rhs = (byte[])obj2;
                        equals = lhs.StructurallyEqual(rhs);
                    }
                    else if (dictionary1 != null)
                    {
                        var dictionary2 = (IDictionary)obj2;

                        equals = AlreadyCompared(dictionary1, dictionary2, recursiveObjects) ||
                                 (PerformListFieldComparison(dictionary1.Keys, dictionary2.Keys, recursiveObjects) &&
                                  (PerformListFieldComparison(dictionary1.Values, dictionary2.Values, recursiveObjects)));
                    }
                    else if (obj1 is IEnumerable && obj1.GetType() != typeof(string))
                    {
                        equals = AlreadyCompared(obj1, obj2, recursiveObjects) || PerformListFieldComparison((IEnumerable)obj1, obj2 as IEnumerable, recursiveObjects);
                    }
                    else if (ShouldFieldHaveValueSemanticEqualityPerformed(obj1.GetType()))
                    {
                        equals = AlreadyCompared(obj1, obj2, recursiveObjects) || PerformFieldByFieldComparision(obj1, obj2, recursiveObjects);
                    }
                    else
                    {
                        equals = obj1.Equals(obj2);
                    }
                }
                else
                {
                    LogFailedComparison("objects not viable for comparison", obj1.GetType(), obj1.GetType(), obj2.GetType());
                    equals = false;
                }
            }

            return(equals);
        }
        private static bool PerformFieldByFieldComparision(object obj1, object obj2, ObjectReferenceDictionary recursiveObjects)
        {
            var equals = true;

            foreach (var currentField in obj1.GetAllFieldsWithoutAttribute <NoValueCompareAttribute>())
            {
                var fieldValue1 = currentField.GetValue(obj1);
                var fieldValue2 = currentField.GetValue(obj2);

                IFieldValueEqualityComparer comparer;

                if (MatchesComparer(ref fieldValue1, ref fieldValue2, obj1, obj2, currentField, out comparer))
                {
                    equals = comparer.Equals(fieldValue1, fieldValue2);
                }
                else if (fieldValue1 as IDictionary != null) // Will not pass if null and can just use normal null checking
                {
                    var dictionary1 = (IDictionary)fieldValue1;
                    var dictionary2 = fieldValue2 as IDictionary;
                    if (dictionary2 == null)
                    {
                        equals = false;
                    }
                    else
                    {
                        equals = AlreadyCompared(dictionary1, dictionary2, recursiveObjects) ||
                                 (PerformListFieldComparison(dictionary1.Keys, dictionary2.Keys, recursiveObjects) &&
                                  (PerformListFieldComparison(dictionary1.Values, dictionary2.Values, recursiveObjects)));
                    }
                }
                else if (typeof(IEnumerable).IsAssignableFrom(currentField.FieldType) && typeof(string) != currentField.FieldType) // Will not pass if null and can just use normal null checking
                {
                    equals = PerformListFieldComparison(fieldValue1 as IEnumerable, fieldValue2 as IEnumerable, recursiveObjects);
                }
                else if ((currentField.FieldType) == typeof(DateTime) || (currentField.FieldType) == typeof(DateTime?))
                {
                    equals = PerformDateTimeFieldComparison(fieldValue1, fieldValue2);
                }
                else if (ShouldFieldHaveValueSemanticEqualityPerformed(currentField))
                {
                    equals = PerformEntityFieldComparison(fieldValue1, fieldValue2, recursiveObjects);
                }
                else
                {
                    equals = PerformBasicFieldComparison(fieldValue1, fieldValue2);
                }

                if (!equals)
                {
                    LogFailedComparison(currentField.Name, obj1.GetType(), fieldValue1, fieldValue2);

                    break;
                }
            }

            return(equals);
        }
        private static bool AlreadyCompared(object target, object toCompare, ObjectReferenceDictionary recursiveObjects)
        {
            var alreadyCompared = false;

            if (recursiveObjects.ContainsKey(target))
            {
                if (ReferenceEquals(recursiveObjects[target], toCompare))
                {
                    alreadyCompared = true;
                }
            }
            else
            {
                recursiveObjects.Add(target, toCompare);
            }

            return(alreadyCompared);
        }
        private static bool CompareListElements(IEnumerable enumerable1, IEnumerable enumerable2, ObjectReferenceDictionary recursiveObjects, ref string failMessage)
        {
            var equal = true;

            var list1 = enumerable1 as IList <object> ?? enumerable1.Cast <object>().ToList();
            var list2 = enumerable2 as IList <object> ?? enumerable2.Cast <object>().ToList();

            if (list1.Count() != list2.Count())
            {
                equal       = false;
                failMessage = $"Expected length {list1.Count} but was {list2.Count}";
            }
            else
            {
                var enumerator1 = list1.GetEnumerator();
                var enumerator2 = list2.GetEnumerator();

                var index = 0;

                while (enumerator1.MoveNext() && enumerator2.MoveNext())
                {
                    var instance1 = enumerator1.Current;
                    var instance2 = enumerator2.Current;

                    IFieldValueEqualityComparer comparer;
                    if (instance1 == null)
                    {
                        equal = (instance2 == null);
                    }
                    else if (MatchesComparer(instance1, instance2, out comparer))
                    {
                        equal = comparer.Equals(instance1, instance2);
                    }
                    else if (ShouldFieldHaveValueSemanticEqualityPerformed(instance1.GetType()))
                    {
                        equal = Compare(instance1, instance2, recursiveObjects);
                    }
                    else
                    {
                        equal = instance1.Equals(instance2);
                    }

                    if (!equal)
                    {
                        failMessage = $"Item's at index:{index} in the lists are not the same";
                        break;
                    }
                    index++;
                }
            }

            return(equal);
        }
        private static bool PerformListFieldComparison(IEnumerable list1, IEnumerable list2, ObjectReferenceDictionary recursiveObjects, bool logOnFail = true)
        {
            bool equals;
            var  failMessage = string.Empty;

            if ((list1 == null) || (list2 == null))
            {
                equals = ((list1 == null) && (list2 == null));
            }
            else if (ReferenceEquals(list1, list2))
            {
                equals = true;
            }
            else
            {
                equals = CompareListElements(list1, list2, recursiveObjects, ref failMessage);
            }

            if (!equals && logOnFail)
            {
                var type = list1?.GetType() ?? list2.GetType();

                LogFailedComparison(failMessage, type, list1, list2);
            }

            return(equals);
        }
        private static bool PerformEntityFieldComparison(object value1, object value2, ObjectReferenceDictionary recursiveObjects)
        {
            bool equals;

            if ((value1 == null) || (value2 == null))
            {
                equals = ((value1 == null) && (value2 == null));
            }
            else
            {
                equals = Compare(value1, value2, recursiveObjects);
            }

            return(equals);
        }