private ObjectComparisonResult AreObjectsInEnumerablesEqual(object value1, object value2)
        {
            var enumerator1 = ((IEnumerable)value1).GetEnumerator();
            var enumerator2 = ((IEnumerable)value2).GetEnumerator();

            bool hasNext1 = enumerator1.MoveNext();
            bool hasNext2 = enumerator2.MoveNext();

            while (hasNext1 && hasNext2)
            {
                var result = AreEqual(enumerator1.Current, enumerator2.Current);
                if (!result.AreEqual)
                {
                    return(result);
                }

                hasNext1 = enumerator1.MoveNext();
                hasNext2 = enumerator2.MoveNext();
            }

            if (hasNext1 != hasNext2)
            {
                var r      = new EnumerableComparisonReport();
                var report = r.Generate("Expected", value1, "Actual", value2);
                return(ObjectComparisonResult.Fail(_propertyComparisonStack, "Enumerable properties have different lengths:\r\n" + report));
            }

            return(ObjectComparisonResult.Success());
        }
        private ObjectComparisonResult ArePrimitivesEqual(object expected, object actual)
        {
            if (!actual.Equals(expected))
            {
                return(ObjectComparisonResult.Fail(_propertyComparisonStack, "Primitive values differ. expected: {0}, actual: {1}.", expected, actual));
            }

            return(ObjectComparisonResult.Success());
        }
        private ObjectComparisonResult AreComplexObjectsEqual(object expected, object actual)
        {
            var typeBeingCompared = expected.GetType();
            var properties        = typeBeingCompared.GetProperties(BindingFlags.Public | BindingFlags.Instance);

            if (false == properties.Any())
            {
                return(ObjectComparisonResult.Success());
            }

            foreach (var property in properties)
            {
                if (false == _ignores.Contains(typeBeingCompared, property.Name))
                {
                    _propertyComparisonStack.Push(property.Name);
                    try
                    {
                        var value1 = property.GetValue(expected, null);

                        if (property.PropertyType.IsPrimitive == false)
                        {
                            if (_circularReferenceChecklist.Contains(value1))
                            {
                                return(ObjectComparisonResult.Success());
                            }

                            _circularReferenceChecklist.Add(value1);
                        }

                        var value2 = property.GetValue(actual, null);

                        // NOTE: Recursion
                        var result = AreEqual(value1, value2);

                        if (!result.AreEqual)
                        {
                            return(result);
                        }
                    }
                    finally
                    {
                        _propertyComparisonStack.Pop();
                    }
                }
            }

            return(ObjectComparisonResult.Success());
        }
        public ObjectComparisonResult AreEqual(object expected, object actual)
        {
            if (expected == null && actual == null)
            {
                return(ObjectComparisonResult.Success());
            }

            var type = (expected ?? actual).GetType();

            if (_propertyComparisonStack.Count == 0)
            {
                _propertyComparisonStack.Push(type.Name);
            }

            if ((expected == null) != (actual == null))
            {
                return(ObjectComparisonResult.Fail(_propertyComparisonStack, "Nullity differs. expected is {0} while actual is {1}.", expected ?? "null", actual ?? "null"));
            }

            var expectedType = expected.GetType();
            var actualType   = actual.GetType();

            if (expectedType != actualType)
            {
                // Let the type check go if they're both at least enumerable
                if (false == (expected.GetType().IsEnumerable() && actual.GetType().IsEnumerable()))
                {
                    return(ObjectComparisonResult.Fail(_propertyComparisonStack, "Objects are of different type. expected is {0} while actual is {1}.", expectedType, actualType));
                }
            }

            if (expectedType.IsPrimitive())
            {
                return(ArePrimitivesEqual(expected, actual));
            }

            if (expectedType.IsEnumerable())
            {
                return(AreObjectsInEnumerablesEqual(expected, actual));
            }

            return(AreComplexObjectsEqual(expected, actual));
        }