private static IEnumerable<ObjectVariance> CheckNullObjectsVariance(object object1,
                                                                            object object2,
                                                                            ObjectVariance parentVariance)
        {
            var propertyInfo1 = object1 as PropertyInfo;
            var propertyInfo2 = object2 as PropertyInfo;

            // One of the objects is null
            if (ReferenceEquals(object1, null) || (propertyInfo1 != null && ReferenceEquals(propertyInfo1.GetValue(parentVariance.PropertyValue1), null)))
            {
                // object2 is a PropertyInfo
                if (propertyInfo2 != null)
                {
                    object property2Value = propertyInfo2.GetValue(parentVariance.PropertyValue2);
                    yield return new ObjectVariance(propertyInfo2.Name, null, property2Value, parentVariance);
                    yield break;
                }

                // object2 is a primitive or string, or DateTime
                yield return new ObjectVariance(null, null, object2, parentVariance);
                yield break;
            }

            if (!ReferenceEquals(object2, null) &&
                (propertyInfo2 == null || !ReferenceEquals(propertyInfo2.GetValue(parentVariance.PropertyValue2), null)))
            {
                yield break;
            }

            // object1 is a PropertyInfo
            if (propertyInfo1 != null)
            {
                object property1Value = propertyInfo1.GetValue(parentVariance.PropertyValue1);
                yield return new ObjectVariance(propertyInfo1.Name, property1Value, null, parentVariance);
                yield break;
            }

            // object1 is a primitive or string, or DateTime
            yield return new ObjectVariance(null, object1, null, parentVariance);
        }
        private static IEnumerable<ObjectVariance> GetVariancesFromProperties(object object1,
                                                                              object object2,
                                                                              ObjectVariance parentVariance)
        {
            // ReferenceEquals instead of == because == may be overridden
            if (ReferenceEquals(object1, null))
            {
                throw new ArgumentNullException(nameof(object1));
            }

            // ReferenceEquals instead of == because == may be overridden
            if (ReferenceEquals(object2, null))
            {
                throw new ArgumentNullException(nameof(object2));
            }

            IEnumerable<PropertyInfo> object1PropertyInfos = ReflectionHelper.GetPropertyInfos(object1.GetType());
            IEnumerable<PropertyInfo> object2PropertyInfos = ReflectionHelper.GetPropertyInfos(object2.GetType());

            using (IEnumerator<PropertyInfo> propertyInfo1Enumerator = object1PropertyInfos.GetEnumerator(),
                                             propertyInfo2Enumerator = object2PropertyInfos.GetEnumerator())
            {
                while (propertyInfo1Enumerator.MoveNext() && propertyInfo2Enumerator.MoveNext())
                {
                    // Found IgnoreVarianceAttribute
                    if (ReflectionHelper.HasIgnoreVarianceAttribute(propertyInfo1Enumerator.Current,
                                                                    propertyInfo2Enumerator.Current))
                    {
                        continue;
                    }

                    foreach (ObjectVariance objectVariance in GetObjectVariances(propertyInfo1Enumerator.Current,
                                                                                 propertyInfo2Enumerator.Current,
                                                                                 parentVariance))
                    {
                        yield return objectVariance;
                    }
                }
            }
        }
        private static IEnumerable<ObjectVariance> GetObjectVariances(object object1,
                                                                      object object2,
                                                                      ObjectVariance parentVariance)
        {
            // Base case where object1 and object2 are PropertyInfo objects already
            string propertyName = null;
            var propertyInfo1 = object1 as PropertyInfo;
            var propertyInfo2 = object2 as PropertyInfo;
            if (propertyInfo1 != null && propertyInfo2 != null && propertyInfo1.Name == propertyInfo2.Name)
            {
                // Found IgnoreVarianceAttribute
                if (ReflectionHelper.HasIgnoreVarianceAttribute(propertyInfo1, propertyInfo2))
                {
                    yield break;
                }

                propertyName = propertyInfo1.Name;

                // ReSharper disable once PossibleNullReferenceException
                object1 = ((PropertyInfo)object1).GetValue(parentVariance.PropertyValue1);
                object2 = ((PropertyInfo)object2).GetValue(parentVariance.PropertyValue2);

                // Check if already traversed
                if (!ReflectionHelper.ShouldIgnoreVariance(object1, object2) && AreObjectsTraversed(object1, object2))
                {
                    yield break;
                }
            }

            // Both objects are null, required for inner property checking
            if (ReferenceEquals(object1, null) && ReferenceEquals(object2, null))
            {
                yield break;
            }

            // Null checks, two scenarios
            // 1. It is a propertyInfo that can be further traversed
            // 2. It is a top level object
            object nullCheck1 = propertyInfo1 ?? object1;
            object nullCheck2 = propertyInfo2 ?? object2;
            foreach (
                ObjectVariance objectVariance in CheckNullObjectsVariance(nullCheck1, nullCheck2, parentVariance))
            {
                // ReSharper disable once PossibleNullReferenceException
                yield return objectVariance;
                yield break;
            }

            // ReSharper disable once PossibleNullReferenceException
            // Checked with ReferenceEquals(object1, null) already
            Type object1Type = object1.GetType();
            Type object2Type = object2.GetType();

            // Special case where the two objects are primitive type, if the two objects are not primitives,
            // there are two possibilities:
            // 1. object1 & object2 are properties to other types which can be further traversed
            // 2. object1 & object2 are top level objects
            if (ReflectionHelper.ShouldIgnoreVariance(object1, object2))
            {
                if (object1.Equals(object2))
                {
                    yield break;
                }

                // parentVariance == null means primitives are being passed to compare
                // propertyName != null means deepest level reached within the object graph
                yield return
                    propertyName != null || parentVariance == null
                        ? new ObjectVariance(propertyName, object1, object2, parentVariance)
                        : parentVariance;
                yield break;
            }

            // parentVariance == null menas primitives are being passed passed to compare
            // propertyName != null means deepest level reached within the object graph
            if (propertyName != null || parentVariance == null)
            {
                parentVariance = new ObjectVariance(propertyName, object1, object2, parentVariance);
            }

            // For both objects implementing IEnumerable<T>
            Type object1GenericArgument, object2GenericArgument;
            if (ReflectionHelper.TryGetIEnumerableGenericArgument(object1Type, out object1GenericArgument) &&
                ReflectionHelper.TryGetIEnumerableGenericArgument(object2Type, out object2GenericArgument) &&
                object1GenericArgument == object2GenericArgument)
            {
                // Found IgnoreVarianceAttribute
                if (ReflectionHelper.HasIgnoreVarianceAttribute(object1GenericArgument))
                {
                    yield break;
                }

                IEnumerable<PropertyInfo> propertyInfos;
                IEnumerable<ObjectVariance> result = ReflectionHelper.TryGetKeyAttriubte(object1GenericArgument,
                                                                                         out propertyInfos)
                                                         ? GetEnumerableVariancesByKey(object1 as IEnumerable,
                                                                                       object2 as IEnumerable,
                                                                                       propertyInfos,
                                                                                       parentVariance)
                                                         : GetEnumerableVariancesByPosition(object1 as IEnumerable,
                                                                                            object2 as IEnumerable,
                                                                                            parentVariance);
                foreach (ObjectVariance objectVariance in result)
                {
                    yield return objectVariance;
                }

                // Required for more than one difference in inner IEnumerables
                yield break;
            }

            // Add to traversed HashSet
            lock (TraversedObjectLock)
            {
                TraversedObjects.Add(object1);
                TraversedObjects.Add(object2);
            }

            // Compare by property
            foreach (
                ObjectVariance objectVariance in
                GetVariancesFromProperties(object1, object2, parentVariance))
            {
                yield return objectVariance;
            }
        }
        private static IEnumerable<ObjectVariance> GetEnumerableVariancesByPosition(IEnumerable object1,
                                                                                    IEnumerable object2,
                                                                                    ObjectVariance parentVariance,
                                                                                    object key = null)
        {
            // Boxing here, but we cannot determine what generic type argument the caller will pass
            List<object> value1List = object1.Cast<object>().ToList();
            List<object> value2List = object2.Cast<object>().ToList();

            // If the count of IEnumerable is not equal, the two IEnumerable are definitely unequal
            if (value1List.Count != value2List.Count)
            {
                yield return
                    new ObjectVariance(
                        $"{(parentVariance != null ? parentVariance.PropertyName : "this")}.Count()",
                        value1List.Count,
                        value2List.Count,
                        parentVariance);

                // Yield break here as there is no reason to compare since their count is different
                yield break;
            }

            // Try to compare by position
            for (int i = 0; i < value1List.Count; i++)
            {
                // Optional key object used in here since GetEnumerableVariancesByKey will eventually route to here
                // propertyName will be assigned "this[i]" when comparing by position or key.ToString() when comparing by key
                string propertyName = key?.ToString() ?? $"this[{i}]";

                if (AreObjectsTraversed(value1List[i], value2List[i]))
                {
                    yield break;
                }

                // As a test variance for each element in the IEnumerable
                var testParentVariance = new ObjectVariance(propertyName,
                                                            value1List[i],
                                                            value2List[i],
                                                            parentVariance);
                foreach (
                    ObjectVariance objectVariance in
                        GetObjectVariances(value1List[i], value2List[i], testParentVariance))
                {
                    yield return objectVariance;
                }
            }
        }
        private static IEnumerable<ObjectVariance> GetEnumerableVariancesByKey(IEnumerable enumerable1,
                                                                               IEnumerable enumerable2,
                                                                               IEnumerable<PropertyInfo> propertyInfos,
                                                                               ObjectVariance parentVariance)
        {
            List<string> propertyInfosList = propertyInfos.Select(p => p.Name).ToList();
            string propertyNames = String.Join(", ", propertyInfosList);

            // Try to group by properties having the KeyAttribute applied using Dynamic LINQ
            // IGroupedEnumerable with Key => Anonymous object with the key properties,
            //                         Count => Count of grouped objects
            //                         Value => The grouped object themselves
            //                         Compared => Boolean flag for determining whether we can skip comparisons or not
            IQueryable query1 =
                enumerable1.AsQueryable()
                           .GroupBy($"new ({propertyNames})", "it")
                           .Select("new (it.Key, it.Count() as Count, it as Value, false as Compared)");
            IQueryable query2 =
                enumerable2.AsQueryable()
                           .GroupBy($"new ({propertyNames})", "it")
                           .Select("new (it.Key, it.Count() as Count, it as Value, false as Compared)");

            // Make sure if query1 contains more items than query2, or vice versa, can be detected
            foreach (
                ObjectVariance objectVariance in
                    KeyPropertiesComparer.GetSetDifferenceVariances(query1, query2, parentVariance))
            {
                yield return objectVariance;
            }

            foreach (dynamic group1 in query1)
            {
                foreach (dynamic group2 in query2)
                {
                    if (group1.Compared || group2.Compared)
                    {
                        continue;
                    }

                    if (!Object.Equals(group1.Key, group2.Key))
                    {
                        continue;
                    }

                    if (group1.Count > 1 || group2.Count > 1)
                    {
                        throw new InvalidOperationException("The IEnumerable contains objects with the same key");
                    }

                    // Here we already guaranteed there is one value per group for each key
                    foreach (
                        ObjectVariance objectVariance in
                            GetEnumerableVariancesByPosition(group1.Value, group2.Value, parentVariance, group1.Key))
                    {
                        yield return objectVariance;
                    }

                    group1.Compared = true;
                    group2.Compared = true;
                }
            }
        }
        public static IEnumerable<ObjectVariance> GetSetDifferenceVariances(IQueryable query1,
                                                                            IQueryable query2,
                                                                            ObjectVariance parentVariance)
        {
            IQueryable keyQuery1 = query1.Select("it.Key");
            IQueryable keyQuery2 = query2.Select("it.Key");

            IEnumerable<object> keyList1 = from dynamic q in keyQuery1 select q;
            IEnumerable<object> keyList2 = from dynamic q in keyQuery2 select q;

            List<object> extraKeysIn1 = keyList1.Except(keyList2).ToList();
            List<object> extraKeysIn2 = keyList2.Except(keyList1).ToList();

            // Extra Keys in query1
            foreach (var extraKey in extraKeysIn1)
            {
                foreach (dynamic group1 in query1)
                {
                    if (group1.Count > 1)
                    {
                        throw new InvalidOperationException("The IEnumerable contains objects with the same key");
                    }

                    if (!Object.Equals(group1.Key, extraKey))
                    {
                        continue;
                    }

                    foreach (var object1 in group1.Value)
                    {
                        string propertyName = parentVariance != null
                                                  ? !String.IsNullOrEmpty(parentVariance.PropertyName)
                                                        ? parentVariance.PropertyName
                                                        : "IEnumerable 1"
                                                  : "IEnumerable 1";
                        yield return
                            new ObjectVariance(String.Format("Extra object in {0} with key {1}", propertyName, group1.Key.ToString()), object1, null, parentVariance);
                    }
                }
            }

            // Extra Keys in query2
            foreach (var extraKey in extraKeysIn2)
            {
                foreach (dynamic group2 in query2)
                {
                    if (group2.Count > 1)
                    {
                        throw new InvalidOperationException("The IEnumerable contains objects with the same key");
                    }

                    if (!Object.Equals(group2.Key, extraKey))
                    {
                        continue;
                    }

                    foreach (var object2 in group2.Value)
                    {
                        string propertyName = parentVariance != null
                                                  ? !String.IsNullOrEmpty(parentVariance.PropertyName)
                                                        ? parentVariance.PropertyName
                                                        : "IEnumerable 2"
                                                  : "IEnumerable 2";
                        yield return new ObjectVariance(String.Format("Extra object in {0} with key {1}", propertyName, group2.Key.ToString()), null, object2, parentVariance);
                    }
                }
            }
        }