internal static void AssertPublicPropertiesEqual(object expected, object actual, string objectDescription, string message, IgnoreProperties ignoreProperties, Stack <object> visitedObjects)
        {
            if (String.IsNullOrEmpty(objectDescription))
            {
                objectDescription = "<object>";
            }
            string assertMsg = (String.IsNullOrEmpty(objectDescription) ? String.Empty : "(Property: " + objectDescription + ")");

            if (!String.IsNullOrEmpty(message))
            {
                assertMsg += (assertMsg.Length > 0 ? " " : String.Empty) + message;
            }

            if ((expected == null) || (actual == null))
            {
                // Either expected or actual is null, so assert that both are.
                Assert.Equal(expected, actual);
            }
            else
            {
                //Neither expected nor actual is null.
                bool haveAddedToVisitedObjects;
                if (StartVisit(expected, visitedObjects, out haveAddedToVisitedObjects))
                {
                    //Looks like the caller's original type contains a circular reference - we have already seen [expected].
                    return;
                }

                //Assert that expected and actual are of the same type.
                Assert.Same(expected.GetType(), actual.GetType());
                Type objectType = expected.GetType();

                bool checkObjectPublicProperties           = !(objectType.IsPrimitive || objectType.IsEnum || (expected is string) || expected is DateTime); // For these types there is no need to check the public properties.
                bool isValueTypeObjectWithoutRefProperties = objectType.IsValueType;                                                                         // While checking public properties, we will keep track of whether the object is a value type that contains only value type properties (in which case we will check bitwise equality).

                if (checkObjectPublicProperties)
                {
                    //See if the caller has supplied an ignore list.
                    bool hasIgnoreList = (ignoreProperties != null) && (ignoreProperties.Count > 0);

                    foreach (PropertyInfo currProperty in objectType.GetProperties())
                    {
                        isValueTypeObjectWithoutRefProperties &= currProperty.PropertyType.IsValueType;

                        MethodInfo getterMethod = currProperty.GetGetMethod();
                        if (getterMethod != null)
                        {
                            bool isStaticProperty  = getterMethod.IsStatic;
                            bool isIndexedProperty = (getterMethod.GetParameters().Length > 0);

                            if ((!isStaticProperty) && (!isIndexedProperty) && (!hasIgnoreList || !ignoreProperties.IgnoreProperty(objectType, currProperty.Name)))
                            {
                                //This is not a static property, not an indexed property and it is not on the ignore list so check it.
                                object expectedPropValue   = currProperty.GetValue(expected, null);
                                object actualPropValue     = currProperty.GetValue(actual, null);
                                string propertyDescription = (objectDescription == null) ? currProperty.Name : String.Format("{0}.{1}", objectDescription, currProperty.Name);

                                AssertPublicPropertiesEqual(expectedPropValue, actualPropValue, propertyDescription, message, ignoreProperties, visitedObjects);
                            } // is non-indexed and not on ignore list
                        }     // has a Get method
                    }         // foreach property in object

                    if (typeof(IEnumerable).IsAssignableFrom(objectType))
                    {
                        //Object is some kind of collection.  Enumerate through all the objects it contains.
                        IEnumerator it_e  = ((IEnumerable)expected).GetEnumerator();
                        IEnumerator it_a  = ((IEnumerable)actual).GetEnumerator();
                        int         count = 0;
                        bool        moreItemsInExpected = true;
                        while (moreItemsInExpected)
                        {
                            if (moreItemsInExpected = it_e.MoveNext())
                            {
                                Assert.True(it_a.MoveNext(), String.Format("Expected more items in collection; actual has only {0} item(s). {1}", count, assertMsg));
                                count++;
                                AssertPublicPropertiesEqual(it_e.Current, it_a.Current, String.Format("{0}[{1}]", objectDescription, count - 1), message, ignoreProperties, visitedObjects);
                            }
                            else
                            {
                                Assert.False(it_a.MoveNext(), String.Format("Expected {0} items in collection, but actual has more than that. {1}", count, assertMsg));
                            }
                        } // while there are more items in the enumeration
                    }     // object is enumerable
                }         // check object's public properties

                bool checkObjectValueEquality = (isValueTypeObjectWithoutRefProperties || (expected is string));
                if (checkObjectValueEquality)
                {
                    Assert.Equal(expected, actual);
                }
                if (haveAddedToVisitedObjects)
                {
                    EndVisit(visitedObjects);
                }
            } // object is not null
        }
 /// <summary>
 /// Asserts that the public properties of two objects are equal (a deep comparison).
 /// </summary>
 /// <param name="expected">The first object to compare. This is the object the unit test expects.</param>
 /// <param name="actual">The second object to compare. This is the object the unit test produced.</param>
 /// <param name="ignoreProperties">An <see cref="IgnoreProperties"/> object that specifies the properties that are to be ignored during the comparison.</param>
 /// <param name="message">A message to display if the assertion fails. This message can be seen in the unit test results.</param>
 public static void AssertPublicPropertiesEqual(object expected, object actual, IgnoreProperties ignoreProperties, string message)
 {
     AssertPublicPropertiesEqual(expected, actual, null, message, null, new Stack <object>());
 }