// -------------------------------------------------------------------------------------------------------------------------- /// <summary> /// Evaluates the input objects for nulls + handles the various conditions. /// </summary> /// <returns>A boolean value indicating whether the caller should continue evaluation or not.</returns> private static bool EvaluateNulls(object source, object comp, string name, InspectionReport report) { if (source == null) { if (comp == null) { // The objects are both null, so we are OK. report.Pass(); return(false); } else { report.Fail(string.Format("The comparison object is null on member '{0}'!", name)); return(false); } } else if (comp == null) { report.Fail(string.Format("The comparison object is null on member '{0}'!", name)); return(false); } // Neither object is null... return(true); }
// -------------------------------------------------------------------------------------------------------------------------- private bool TypesMatch(object source, object comp, InspectionReport report) { Type sType = source.GetType(); Type cType = comp.GetType(); if (sType != cType) { string errMsg = "The source and comparison types are not the same! Expected '{0}' and found '{1}'"; report.Fail(string.Format(errMsg, sType, cType)); return(false); } return(true); }
// -------------------------------------------------------------------------------------------------------------------------- public InspectionReport CompareObjects(Type type, object source, object comp, bool throwOnFail = false) { CachedReports = new MultiDictionary <object, object, InspectionReport>(); SrcRefCache = new PairDictionary <object, int>(); CompRefCache = new PairDictionary <object, int>(); InspectionReport res = InternalCompare(type, source, comp, null, throwOnFail); // Cleanup and return... CachedReports.Clear(); CachedReports = null; return(res); }
// -------------------------------------------------------------------------------------------------------------------------- private void CompareArrays(Type type, object source, object comp, InspectionReport report) { Type arrayType = type.GetElementType(); var listMethod = ArrayCompare.MakeGenericMethod(new[] { arrayType }); var listComp = (Tuple <bool, string>)listMethod.Invoke(this, new[] { (object)source, (object)comp, null }); bool match = listComp.Item1; if (!match) { report.Fail(null); } else { report.Pass(); report.Message = listComp.Item2; } }
// -------------------------------------------------------------------------------------------------------------------------- private void CompareLists(Type type, object source, object comp, InspectionReport report) { #if !NETFX_CORE Type listType = type.GetGenericArguments()[0]; #else Type listType = type.GetTypeInfo().GenericTypeArguments[0]; #endif var listMethod = ListCompare.MakeGenericMethod(new[] { listType }); var listComp = (Tuple <bool, string>)listMethod.Invoke(this, new[] { (object)source, (object)comp, null }); bool match = listComp.Item1; if (!match) { report.Fail(null); } else { report.Pass(); report.Message = listComp.Item2; } }
// -------------------------------------------------------------------------------------------------------------------------- private void CompareDictionaries(Type type, object source, object comp, InspectionReport report) { // Also, handle it... #if !NETFX_CORE Type[] args = type.GetGenericArguments(); #else Type[] args = type.GetTypeInfo().GenericTypeArguments; #endif var compMethod = DictionaryCompare.MakeGenericMethod(args); var dictComp = (Tuple <bool, string>)compMethod.Invoke(this, new[] { source, comp, null, null }); bool match = dictComp.Item1; if (!match) { report.Fail(null); } else { report.Pass(); report.Message = dictComp.Item2; } }
// -------------------------------------------------------------------------------------------------------------------------- internal InspectionReport InternalCompare(Type type, object source, object comp, string name, bool throwOnFail = false) { InspectionReport report = new InspectionReport(name, throwOnFail) { SourceObject = source, CompObject = comp, }; // TODO: We really need to clean this method up! // In particular, it should be broken into sub methods, and we should really reduce the number // of exit points as they can make this very confusing indeed!! if (IgnoredTypes.Contains(type)) { string skipMessage = "The type '{0}' is set to be ignored, and so the member '{1}' has been excluded from the inspection"; report.Skip(string.Format(skipMessage, type, name)); return(report); } // One or more nulls require some special handling... bool continueEval = EvaluateNulls(source, comp, name, report); if (!continueEval) { return(report); } if (!TypesMatch(source, comp, report)) { return(report); } // Since both items are null, we can see if we have already cached this data.... if (CachedReports.ContainsKey(source, comp)) { return(CachedReports[source, comp]); } CachedReports.Add(source, comp, report); if (Comparers.ContainsKey(type)) { bool match = (bool)Comparers[type].DynamicInvoke(source, comp); if (!match) { report.Fail(null); } report.Pass(); return(report); } if (ReflectionTools.IsSimpleType(type)) { // NOTE: 3.14.2013 --> Technically strings are reference types, but I can't think of a way to get compare // their references in a reasonable way at this time. I am sure that it is possible, but // I am still going to treat them as 'primitve' items. bool match = source.Equals(comp); if (!match) { report.Fail(null); } else { report.Pass(); } return(report); } // Do the reference comparison. bool graphOK = CheckReferenceGraph(source, comp); if (!graphOK) { report.Fail(string.Format("Invalid reference at item '{0}'!", name)); return(report); } if (IsArray(type)) { CompareArrays(type, source, comp, report); return(report); } if (IsIList(type)) { CompareLists(type, source, comp, report); return(report); } if (IsIDictionary(type)) { CompareDictionaries(type, source, comp, report); return(report); } // We will look at each property and determine if it is a value type, or string. // If it is, then we can just do a normal equality test. // If not, we will continue to decompose the object. // Same goes for the fields. List <PropertyInfo> props = ResolveProperties(type); List <FieldInfo> fields = ResolveFields(type); // NOTE: In the WinRT version, we simply get all of the non public members in the above calls. We should probably port the Win32 code to do the same thing. List <TypeMember> nonPublics = ResolvePrivateMembers(type); List <TypeMember> allMembers = (from x in props select new TypeMember(x)).Concat( (from x in fields select new TypeMember(x))).ToList(); foreach (var item in nonPublics) { allMembers.Add(item); } int memberCount = allMembers.Count; for (int i = 0; i < memberCount; i++) { string memberName = allMembers[i].Name; if (allMembers[i].IsIndexer) { InspectionReport memberReport = new InspectionReport(memberName, throwOnFail); memberReport.Skip(string.Format("The member '{0}' is an indexer property and will be ignored.", memberName)); report.AddMemberReport(memberReport); continue; } object srcVal = allMembers[i].GetValue(source); object compVal = allMembers[i].GetValue(comp); try { InspectionReport memberReport = null; object res = null; var caller = RecursiveCompare; try { res = caller.Invoke(this, new[] { allMembers[i].Type, srcVal, compVal, memberName, throwOnFail }); } catch (TargetInvocationException ex) { // Unwrap and rethrow basically. if (ex.InnerException.GetType() == typeof(ObjectInspectorException)) { throw ex.InnerException; } throw; } memberReport = (InspectionReport)res; report.AddMemberReport(memberReport); // NOTE: This was the old condition. It turned out that sometimes, during recursive compares, // we would have non-matching objects, though their evaluation wasn't complete. This made it // seem as though the items did not match at all.... //if (memberReport.ObjectsMatch == false) // So I changed it to this. This seems like the right thing to do. Instead of bailing right away, // let it finish doing its thing.... if (memberReport.ObjectsMatch == false) // && memberReport.InspectionComplete) { string errMsg = "The member values for '{0}' on type '{1}' do not match!\r\n{2}"; report.Fail(string.Format(errMsg, memberName, type, memberReport.Message)); return(report); } } catch (Exception ex) { throw; string errMsg = "The inspection of the member '{0}' on Type '{1}' failed!\r\n" + "Message:\r\n{2}"; report.Fail(string.Format(errMsg, memberName, type, ex.Message)); return(report); } } report.Pass(); return(report); }
// -------------------------------------------------------------------------------------------------------------------------- public void AddMemberReport(InspectionReport report) { _MemberReports.Add(report); }