/// <summary>Assert by deep recursive value equality compare.</summary>
        public static void IsNotStructuralEqual(this object actual, object expected, string message = "")
        {
            message = string.IsNullOrEmpty(message) ? string.Empty : ", " + message;
            if (object.ReferenceEquals(actual, expected))
            {
                throw new AssertException("actual is same reference" + message);
            }

            if (actual == null)
            {
                return;
            }

            if (expected == null)
            {
                return;
            }

            if (actual.GetType() != expected.GetType())
            {
                return;
            }

            EqualInfo r = StructuralEqual(actual, expected, new[] { actual.GetType().Name }); // root type

            if (r.IsEquals)
            {
                throw new AssertException("is structural equal" + message);
            }
        }
        private static EqualInfo SequenceEqual(IEnumerable leftEnumerable, IEnumerable rightEnumarable, IEnumerable <string> names)
        {
            IEnumerator le = leftEnumerable.GetEnumerator();

            using (le as IDisposable)
            {
                IEnumerator re = rightEnumarable.GetEnumerator();

                using (re as IDisposable)
                {
                    var index = 0;
                    while (true)
                    {
                        object lValue = null;
                        object rValue = null;
                        var    lMove  = le.MoveNext();
                        var    rMove  = re.MoveNext();
                        if (lMove)
                        {
                            lValue = le.Current;
                        }

                        if (rMove)
                        {
                            rValue = re.Current;
                        }

                        if (lMove && rMove)
                        {
                            EqualInfo result = StructuralEqual(lValue, rValue, names.Concat(new[] { "[" + index + "]" }));
                            if (!result.IsEquals)
                            {
                                return(result);
                            }
                        }

                        if ((lMove == true && rMove == false) || (lMove == false && rMove == true))
                        {
                            return(new EqualInfo {
                                IsEquals = false, Left = lValue, Right = rValue, Names = names.Concat(new[] { "[" + index + "]" })
                            });
                        }

                        if (lMove == false && rMove == false)
                        {
                            break;
                        }

                        index++;
                    }
                }
            }

            return(new EqualInfo {
                IsEquals = true, Left = leftEnumerable, Right = rightEnumarable, Names = names
            });
        }
        /// <summary>Assert by deep recursive value equality compare.</summary>
        public static void IsStructuralEqual(this object actual, object expected, string message = "")
        {
            message = string.IsNullOrEmpty(message) ? string.Empty : ", " + message;
            if (object.ReferenceEquals(actual, expected))
            {
                return;
            }

            if (actual == null)
            {
                throw new AssertException("actual is null" + message);
            }

            if (expected == null)
            {
                throw new AssertException("actual is not null" + message);
            }

            if (actual.GetType() != expected.GetType())
            {
                var msg = string.Format(
                    "expected type is {0} but actual type is {1}{2}",
                    expected.GetType().Name,
                    actual.GetType().Name,
                    message);
                throw new AssertException(msg);
            }

            EqualInfo r = StructuralEqual(actual, expected, new[] { actual.GetType().Name }); // root type

            if (!r.IsEquals)
            {
                var msg = string.Format(
                    "is not structural equal, failed at {0}, actual = {1} expected = {2}{3}",
                    string.Join(".", r.Names),
                    r.Left,
                    r.Right,
                    message);
                throw new AssertException(msg);
            }
        }
        private static EqualInfo StructuralEqual(object left, object right, IEnumerable <string> names)
        {
            // type and basic checks
            if (object.ReferenceEquals(left, right))
            {
                return(new EqualInfo {
                    IsEquals = true, Left = left, Right = right, Names = names
                });
            }

            if (left == null || right == null)
            {
                return(new EqualInfo {
                    IsEquals = false, Left = left, Right = right, Names = names
                });
            }

            Type lType = left.GetType();
            Type rType = right.GetType();

            if (lType != rType)
            {
                return(new EqualInfo {
                    IsEquals = false, Left = left, Right = right, Names = names
                });
            }

            Type type = left.GetType();

            // not object(int, string, etc...)
            if (Type.GetTypeCode(type) != TypeCode.Object)
            {
                return(new EqualInfo {
                    IsEquals = left.Equals(right), Left = left, Right = right, Names = names
                });
            }

            // is sequence
            if (typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(type))
            {
                return(SequenceEqual((IEnumerable)left, (IEnumerable)right, names));
            }

            // IEquatable<T>
            Type equatable = typeof(IEquatable <>).MakeGenericType(type);

            if (equatable.GetTypeInfo().IsAssignableFrom(type))
            {
                var result = (bool)equatable.GetTypeInfo().GetMethod("Equals").Invoke(left, new[] { right });
                return(new EqualInfo {
                    IsEquals = result, Left = left, Right = right, Names = names
                });
            }

            // is object
            FieldInfo[] fields = left.GetType().GetTypeInfo().GetFields(BindingFlags.Instance | BindingFlags.Public);
            IEnumerable <PropertyInfo> properties = left.GetType().GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                                    .Where(x => x.GetGetMethod(false) != null && x.GetIndexParameters().Length == 0);
            IEnumerable <MemberInfo> members = fields.Cast <MemberInfo>().Concat(properties);

            foreach (dynamic mi in members)
            {
                IEnumerable <string> concatNames = names.Concat(new[] { (string)mi.Name });

                object    lv     = mi.GetValue(left);
                object    rv     = mi.GetValue(right);
                EqualInfo result = StructuralEqual(lv, rv, concatNames);
                if (!result.IsEquals)
                {
                    return(result);
                }
            }

            return(new EqualInfo {
                IsEquals = true, Left = left, Right = right, Names = names
            });
        }