diff --git a/FSI.BT.Tools/App.xaml.cs b/FSI.BT.Tools/App.xaml.cs
index 2aaef5a..828afdd 100644
--- a/FSI.BT.Tools/App.xaml.cs
+++ b/FSI.BT.Tools/App.xaml.cs
@@ -1,8 +1,12 @@
using Hardcodet.Wpf.TaskbarNotification;
using NHotkey;
using NHotkey.Wpf;
+using System.Collections;
using System.Windows;
using System.Windows.Input;
+using FSI.Lib.Helpers;
+using FSI.Lib.CompareNetObjects;
+using FSI.Lib.Guis.SetSizePosExWindow;
namespace FSI.BT.Tools
{
@@ -31,13 +35,24 @@ namespace FSI.BT.Tools
Global.FrmRadialMenu = new FrmRadialMenu();
- Global.WinCC = new Lib.Guis.SieTiaWinCCMsgMgt.WinCC(
+ Global.WinCC = new Lib.Guis.SieTiaWinCCMsgMgt.ViewModel.ViewModelWinCC(
Global.Settings.SieTiaWinCCMsgMgtAutostart,
Global.Settings.SieTiaWinCCMsgMgtUpdateIntervall,
Global.Settings.SieTiaWinCCMsgMgtWindowsName,
Global.Settings.SieTiaWinCCMsgMgtClassName,
Global.Settings.SieTiaWinCCMsgMgtBtnName
- );
+ );
+
+ Global.Iba = new Lib.Guis.IbaDirSync.ViewModel.ViewModelIba(
+ Global.Settings.IbaRecordDestinationath,
+ Global.Settings.IbaRecordSourcePath,
+ Global.Settings.IbaAutoSync
+ );
+
+ Global.WindowMgt = new Lib.Guis.SetSizePosExWindow.ViewModel.ViewModelWindow();
+ Global.WindowMgt.AutoStart = Global.Settings.WindowMgtAutostart;
+ Global.WindowMgt.UpdateIntervall = Global.Settings.WindowMgtUpdateInterval;
+
}
private void ShowRadialMenu(object sender, HotkeyEventArgs e)
@@ -60,7 +75,22 @@ namespace FSI.BT.Tools
private void Application_Exit(object sender, ExitEventArgs e)
{
- Global.Settings.Save();
+ AppSettings tmpSetting = new AppSettings(Global.Settings.FileName);
+ tmpSetting.Load();
+
+ CompareLogic compareLogic = new CompareLogic();
+ ComparisonResult result = compareLogic.Compare(Global.Settings, tmpSetting);
+ if (!result.AreEqual)
+ {
+ Global.Settings.Save();
+ }
+
+ if (Global.Iba.RoboCopy != null)
+ {
+ Global.Iba.RoboCopy.Stop();
+ Global.Iba.RoboCopy.Dispose();
+ }
}
+
}
}
\ No newline at end of file
diff --git a/FSI.BT.Tools/AppSettings.cs b/FSI.BT.Tools/AppSettings.cs
index ba48a98..569e6b0 100644
--- a/FSI.BT.Tools/AppSettings.cs
+++ b/FSI.BT.Tools/AppSettings.cs
@@ -1,10 +1,11 @@
using FSI.Lib.WinSettings;
+using System.Collections.ObjectModel;
namespace FSI.BT.Tools
{
public class AppSettings : XmlSettings
{
-
+
public AppSettings(string fileName) : base(fileName)
{
TimeStampFormat = "_yyyyMMdd_HHmmss";
@@ -15,9 +16,9 @@ namespace FSI.BT.Tools
[EncryptedSetting]
public string[] Users { get; set; }
[EncryptedSetting]
- public string[] Admins{ get; set; }
+ public string[] Admins { get; set; }
[EncryptedSetting]
- public string SuperAdmin{ get; set; }
+ public string SuperAdmin { get; set; }
public string TimeStampFormat { get; set; }
public string[] SieSimaticManagerExe { get; set; }
public string[] SieTiaV13Exe { get; set; }
@@ -62,5 +63,16 @@ namespace FSI.BT.Tools
public string SieTiaWinCCMsgMgtWindowsName { get; set; }
public string SieTiaWinCCMsgMgtClassName { get; set; }
public string SieTiaWinCCMsgMgtBtnName { get; set; }
+ public bool IbaAutoSync { get; set; }
+ public string IbaRecordSourcePath { get; set; }
+ public string IbaRecordDestinationath { get; set; }
+ public string[] WindowMgtName { get; set; }
+ public string[] WindowMgtClassName { get; set; }
+ public int WindowMgtUpdateInterval { get; set; }
+ public bool WindowMgtAutostart { get; set; }
+ public int[] WindowMgtX { get; set; }
+ public int[] WindowMgtY { get; set; }
+ public int[] WindowMgtHeight { get; set; }
+ public int[] WindowMgtWight { get; set; }
}
}
diff --git a/FSI.BT.Tools/Commands/LoginCommand.cs b/FSI.BT.Tools/Commands/LoginCommand.cs
index 23bdb5d..376fc36 100644
--- a/FSI.BT.Tools/Commands/LoginCommand.cs
+++ b/FSI.BT.Tools/Commands/LoginCommand.cs
@@ -12,7 +12,7 @@ namespace FSI.BT.Tools.Commands
Lib.Guis.AutoPw.FrmMain frmMain = new Lib.Guis.AutoPw.FrmMain()
{
CloseAtLostFocus = false,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ WindowStartupLocation = WindowStartupLocation.CenterScreen,
};
frmMain.ShowDialog();
diff --git a/FSI.BT.Tools/Commands/OpenAppCommand.cs b/FSI.BT.Tools/Commands/OpenAppCommand.cs
index 9eb8988..bcce347 100644
--- a/FSI.BT.Tools/Commands/OpenAppCommand.cs
+++ b/FSI.BT.Tools/Commands/OpenAppCommand.cs
@@ -57,7 +57,7 @@ namespace FSI.BT.Tools.Commands
{
ShowPdf = false,
CloseAtLostFocus = true,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ WindowStartupLocation = WindowStartupLocation.CenterScreen,
Path = Global.Settings.EplPrjPath,
EplExes = Global.Settings.EplExe,
};
@@ -69,7 +69,7 @@ namespace FSI.BT.Tools.Commands
{
ShowPdf = true,
CloseAtLostFocus = true,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ WindowStartupLocation = WindowStartupLocation.CenterScreen,
Path = Global.Settings.EplPdfPath,
};
frmMainEplPdf.Show();
@@ -114,7 +114,7 @@ namespace FSI.BT.Tools.Commands
{
Password = GetType().Namespace.ToString(),
CloseAtLostFocus = true,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ WindowStartupLocation = WindowStartupLocation.CenterScreen,
};
frmMainDeEnCrypt.Show();
return;
diff --git a/FSI.BT.Tools/Commands/ProcessCommand.cs b/FSI.BT.Tools/Commands/ProcessCommand.cs
index 560e282..d348b29 100644
--- a/FSI.BT.Tools/Commands/ProcessCommand.cs
+++ b/FSI.BT.Tools/Commands/ProcessCommand.cs
@@ -1,4 +1,5 @@
using System.Windows;
+using System.Windows.Data;
namespace FSI.BT.Tools.Commands
{
@@ -10,9 +11,33 @@ namespace FSI.BT.Tools.Commands
public override void Execute(object parameter)
{
Gui.FrmProcesses frm = new Gui.FrmProcesses();
+ frm.WinCC = Global.WinCC;
+ frm.Iba = Global.Iba;
+ Global.Window.Load();
+ frm.WindowMgt = Global.WindowMgt;
+ frm.Closed += Frm_Closed;
frm.ShowDialog();
}
+ private void Frm_Closed(object sender, System.EventArgs e)
+ {
+ Global.WinCC = ((Gui.FrmProcesses)sender).WinCC;
+
+ Global.Settings.SieTiaWinCCMsgMgtAutostart = Global.WinCC.WinCC.AutoStart;
+ Global.Settings.SieTiaWinCCMsgMgtUpdateIntervall = Global.WinCC.WinCC.UpdateIntervall;
+ Global.Settings.SieTiaWinCCMsgMgtWindowsName = Global.WinCC.WinCC.WindowsName;
+ Global.Settings.SieTiaWinCCMsgMgtClassName = Global.WinCC.WinCC.WindowsClassName;
+ Global.Settings.SieTiaWinCCMsgMgtBtnName = Global.WinCC.WinCC.ButtonName;
+
+ Global.Iba = ((Gui.FrmProcesses)sender).Iba;
+ Global.Settings.IbaRecordDestinationath = Global.Iba.Iba.Destination;
+ Global.Settings.IbaRecordSourcePath = Global.Iba.Iba.Source;
+ Global.Settings.IbaAutoSync = Global.Iba.Iba.AutoStart;
+
+ Global.WindowMgt = ((Gui.FrmProcesses)sender).WindowMgt;
+ Global.Window.Save();
+ }
+
public override bool CanExecute(object parameter)
{
return Global.AdminRights;
diff --git a/FSI.BT.Tools/FSI.BT.Tools.xml b/FSI.BT.Tools/FSI.BT.Tools.xml
index 9a29e0e..053807b 100644
--- a/FSI.BT.Tools/FSI.BT.Tools.xml
+++ b/FSI.BT.Tools/FSI.BT.Tools.xml
@@ -2,7 +2,9 @@
+I945AMzKKYBAAAAB21haWVyX3M=
e+Dt7FRUDDoBAAAAB21haWVyX3M=
+
_yyyyMMdd_HHmmss
+
C:\Program Files (x86)\Siemens\Step7\S7BIN\S7tgtopx.exe
C:\Program Files (x86)\Siemens\Automation\Portal V13\Bin\Siemens.Automation.Portal.exe
C:\Program Files\Siemens\Automation\Portal V14\Bin\Siemens.Automation.Portal.exe
@@ -29,6 +31,7 @@
C:\Program Files\RealVNC\VNC Viewer\vncviewer.exe,c:\Users\maier_s\OneDrive - Fondium Group GmbH\Documents\Apps\VNC-Viewer-6.20.113-Windows-64bit.exe
C:\Program Files\RealVNC\VNC Viewer\vncaddrbook.exe
C:\Program Files\iba\ibaAnalyzer\ibaAnalyzer.exe
+
http://desiaugetwf/web/?AspxAutoDetectCookieSupport=1
http://10.10.1.42/SKSchichtbuchWeb/de-DE/Plugin/ShiftBook/ShiftBook/IR
http://10.10.1.42/SKChangeTrackerWeb/de-DE/Plugin/ChangeTracker
@@ -39,11 +42,17 @@
http://desiaugetc7-088:3000/
http://desiaugetc7-088:3001/en/home
https://mingle-portal.eu1.inforcloudsuite.com/FONDIUM_prd
+
\\10.10.1.40\Betriebstechnik\Eplan
\\fondium.org\DESI$\AUG_Abteilung\Betriebstechnik\EPL\P8\Data\Projekte\FSI\
+
true
10
#32770
Zur Kenntnis genommen
+
+ true
+ d:\tmp
+ c:\tmp
\ No newline at end of file
diff --git a/FSI.BT.Tools/FrmRadialMenu.xaml.cs b/FSI.BT.Tools/FrmRadialMenu.xaml.cs
index 9c564b0..a077c8a 100644
--- a/FSI.BT.Tools/FrmRadialMenu.xaml.cs
+++ b/FSI.BT.Tools/FrmRadialMenu.xaml.cs
@@ -18,7 +18,7 @@ namespace FSI.BT.Tools
InitializeComponent();
DataContext = this;
_isOpenHome = true;
- tbversion.Text = "v" + Assembly.GetExecutingAssembly().GetName().Version.Major + "." + Assembly.GetExecutingAssembly().GetName().Version.Minor;
+ tbversion.Text = "v" + Assembly.GetExecutingAssembly().GetName().Version.Major + "." + Assembly.GetExecutingAssembly().GetName().Version.Minor + "b";
}
#region Home
diff --git a/FSI.BT.Tools/Global.cs b/FSI.BT.Tools/Global.cs
index 0feecf0..a50041c 100644
--- a/FSI.BT.Tools/Global.cs
+++ b/FSI.BT.Tools/Global.cs
@@ -1,5 +1,6 @@
-using FSI.Lib.Guis.SieTiaWinCCMsgMgt;
-using FSI.Lib.WinSettings;
+using FSI.Lib.Guis.IbaDirSync.ViewModel;
+using FSI.Lib.Guis.SetSizePosExWindow.ViewModel;
+using FSI.Lib.Guis.SieTiaWinCCMsgMgt.ViewModel;
using Hardcodet.Wpf.TaskbarNotification;
namespace FSI.BT.Tools
@@ -8,10 +9,45 @@ namespace FSI.BT.Tools
{
public static FrmRadialMenu FrmRadialMenu { get; set; }
public static TaskbarIcon TaskbarIcon { get; set; }
- public static WinCC WinCC { get; set; }
+ public static ViewModelWinCC WinCC { get; set; }
public static AppSettings Settings { get; set; }
+ public static ViewModelIba Iba { get; set; }
+ public static ViewModelWindow WindowMgt { get; set; }
public static bool UserRights { get; set; }
public static bool AdminRights { get; set; }
public static bool SuperAdminRights { get; set; }
+
+ public static class Window
+ {
+ public static void Load()
+ {
+ for (int i = 0; i < Global.Settings.WindowMgtName.Length; i++)
+ {
+ WindowMgt.Windows.Add(new Lib.Guis.SetSizePosExWindow.Model.Window
+ {
+ Name = Global.Settings.WindowMgtName[i],
+ ClassName = Global.Settings.WindowMgtClassName[i],
+ Height = Global.Settings.WindowMgtHeight[i],
+ Width = Global.Settings.WindowMgtWight[i],
+ X = Global.Settings.WindowMgtX[i],
+ Y = Global.Settings.WindowMgtY[i],
+ });
+ }
+ }
+
+ public static void Save()
+ {
+ for (int i = 0; i < Global.WindowMgt.Windows.Count; i++)
+ {
+ Global.Settings.WindowMgtName[i] = Global.WindowMgt.Windows[i].Name;
+ Global.Settings.WindowMgtClassName[i] = Global.WindowMgt.Windows[i].ClassName;
+ Global.Settings.WindowMgtHeight[i] = Global.WindowMgt.Windows[i].Height;
+ Global.Settings.WindowMgtWight[i] = Global.WindowMgt.Windows[i].Width;
+ Global.Settings.WindowMgtX[i] = Global.WindowMgt.Windows[i].X;
+ Global.Settings.WindowMgtY[i] = Global.WindowMgt.Windows[i].Y;
+ }
+ }
+ }
+
}
}
diff --git a/FSI.BT.Tools/Gui/FrmProcesses.xaml b/FSI.BT.Tools/Gui/FrmProcesses.xaml
index 9b123f6..1fd2053 100644
--- a/FSI.BT.Tools/Gui/FrmProcesses.xaml
+++ b/FSI.BT.Tools/Gui/FrmProcesses.xaml
@@ -5,33 +5,311 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FSI.BT.Tools.Gui"
mc:Ignorable="d"
- Title="FrmProcesses"
- Height="450"
- Width="800">
+ Title="FSI.BT.Tools Prozesse"
+ SizeToContent="WidthAndHeight"
+ Height="Auto"
+ Width="Auto">
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FSI.BT.Tools/Gui/FrmProcesses.xaml.cs b/FSI.BT.Tools/Gui/FrmProcesses.xaml.cs
index ae3e2cd..7a9c802 100644
--- a/FSI.BT.Tools/Gui/FrmProcesses.xaml.cs
+++ b/FSI.BT.Tools/Gui/FrmProcesses.xaml.cs
@@ -1,4 +1,7 @@
-using System;
+using FSI.Lib.Guis.IbaDirSync.ViewModel;
+using FSI.Lib.Guis.SetSizePosExWindow.ViewModel;
+using FSI.Lib.Guis.SieTiaWinCCMsgMgt.ViewModel;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -19,9 +22,15 @@ namespace FSI.BT.Tools.Gui
///
public partial class FrmProcesses : Window
{
+ public ViewModelWinCC WinCC { get; set; }
+ public ViewModelIba Iba { get; set; }
+
+ public ViewModelWindow WindowMgt { get; set; }
+
public FrmProcesses()
{
- InitializeComponent();
+ InitializeComponent();
+ DataContext = this;
}
}
}
diff --git a/FSI.BT.Tools/NotifyIconResources.xaml b/FSI.BT.Tools/NotifyIconResources.xaml
index 79102be..c8e2bb3 100644
--- a/FSI.BT.Tools/NotifyIconResources.xaml
+++ b/FSI.BT.Tools/NotifyIconResources.xaml
@@ -21,11 +21,11 @@
-
-
+
+
diff --git a/FSI.Lib/FSI.Lib/CompareNetObjects/Cache.cs b/FSI.Lib/FSI.Lib/CompareNetObjects/Cache.cs
new file mode 100644
index 0000000..ec1c5df
--- /dev/null
+++ b/FSI.Lib/FSI.Lib/CompareNetObjects/Cache.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace FSI.Lib.CompareNetObjects
+{
+ ///
+ /// Cache for properties, fields, and methods to speed up reflection
+ ///
+ internal static class Cache
+ {
+ ///
+ /// Reflection Cache for property info
+ ///
+ private static readonly Dictionary _propertyCache;
+
+ ///
+ /// Reflection Cache for field info
+ ///
+ private static readonly Dictionary _fieldCache;
+
+ ///
+ /// Reflection Cache for methods
+ ///
+ private static readonly Dictionary _methodList;
+
+ ///
+ /// Static constructor
+ ///
+ static Cache()
+ {
+ _propertyCache = new Dictionary();
+ _fieldCache = new Dictionary();
+ _methodList = new Dictionary();
+ }
+
+ ///
+ /// Clear the cache
+ ///
+ public static void ClearCache()
+ {
+ lock(_propertyCache)
+ _propertyCache.Clear();
+
+ lock(_fieldCache)
+ _fieldCache.Clear();
+
+ lock(_methodList)
+ _methodList.Clear();
+ }
+
+ ///
+ /// Get a list of the fields within a type
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable GetFieldInfo(ComparisonConfig config, Type type)
+ {
+ lock (_fieldCache)
+ {
+ bool isDynamicType = TypeHelper.IsDynamicObject(type);
+
+ if (config.Caching && _fieldCache.ContainsKey(type))
+ return _fieldCache[type];
+
+ FieldInfo[] currentFields;
+
+#if !NETSTANDARD1_3
+ //All the implementation examples that I have seen for dynamic objects use private fields or properties
+ if (( config.ComparePrivateFields || isDynamicType) && !config.CompareStaticFields)
+ {
+ List list = new List();
+ Type t = type;
+ do
+ {
+ list.AddRange(t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
+ t = t.BaseType;
+ } while (t != null);
+ currentFields = list.ToArray();
+ }
+ else if ((config.ComparePrivateFields || isDynamicType) && config.CompareStaticFields)
+ {
+ List list = new List();
+ Type t = type;
+ do
+ {
+ list.AddRange(
+ t.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic |
+ BindingFlags.Static));
+ t = t.BaseType;
+ } while (t != null);
+ currentFields = list.ToArray();
+ }
+ else
+#endif
+ currentFields = type.GetFields(); //Default is public instance and static
+
+ if (config.Caching)
+ _fieldCache.Add(type, currentFields);
+
+ return currentFields;
+ }
+ }
+
+
+
+ ///
+ /// Get the value of a property
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static object GetPropertyValue(ComparisonConfig config, Type type, object objectValue, string propertyName)
+ {
+ lock (_propertyCache)
+ return GetPropertyInfo(config, type).First(o => o.Name == propertyName).GetValue(objectValue, null);
+ }
+
+ ///
+ /// Get a list of the properties in a type
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable GetPropertyInfo(ComparisonConfig config, Type type)
+ {
+ lock (_propertyCache)
+ {
+ bool isDynamicType = TypeHelper.IsDynamicObject(type);
+
+ if (config.Caching && _propertyCache.ContainsKey(type))
+ return _propertyCache[type];
+
+ PropertyInfo[] currentProperties;
+
+#if NETSTANDARD1_3
+ if (!config.CompareStaticProperties)
+ currentProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
+ else
+ currentProperties = type.GetProperties(); //Default is public instance and static
+#else
+ //All the implementation examples that I have seen for dynamic objects use private fields or properties
+ if ((config.ComparePrivateProperties || isDynamicType) && !config.CompareStaticProperties)
+ {
+ currentProperties =
+ type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+ }
+ else if ((config.ComparePrivateProperties || isDynamicType) && config.CompareStaticProperties)
+ {
+ currentProperties =
+ type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic |
+ BindingFlags.Static);
+ }
+ else if (!config.CompareStaticProperties)
+ {
+ currentProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
+ }
+ else
+ {
+ currentProperties = type.GetProperties(); //Default is public instance and static
+ }
+#endif
+
+ if (config.Caching)
+ {
+ _propertyCache.Add(type, currentProperties);
+ }
+
+ return currentProperties;
+ }
+ }
+
+ ///
+ /// Get a method by name
+ ///
+ ///
+ ///
+ ///
+ public static MethodInfo GetMethod(Type type, string methodName)
+ {
+ lock (_methodList)
+ return GetMethods(type).FirstOrDefault(m => m.Name == methodName);
+ }
+
+ ///
+ /// Get the cached methods for a type
+ ///
+ ///
+ ///
+ public static IEnumerable GetMethods(Type type)
+ {
+ lock (_methodList)
+ {
+ if (_methodList.ContainsKey(type))
+ return _methodList[type];
+
+ MethodInfo[] myMethodInfo = type.GetMethods();
+ _methodList.Add(type, myMethodInfo);
+ return myMethodInfo;
+ }
+ }
+
+ }
+}
diff --git a/FSI.Lib/FSI.Lib/CompareNetObjects/CompareException.cs b/FSI.Lib/FSI.Lib/CompareNetObjects/CompareException.cs
new file mode 100644
index 0000000..e160f90
--- /dev/null
+++ b/FSI.Lib/FSI.Lib/CompareNetObjects/CompareException.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace FSI.Lib.CompareNetObjects
+{
+ ///
+ /// CompareException with a Result Property
+ ///
+ public class CompareException : Exception
+ {
+ ///
+ /// CompareException Constructor
+ ///
+ ///
+ ///
+ public CompareException(ComparisonResult result, string message) : base(message)
+ {
+ Result = result;
+ }
+
+ ///
+ /// The comparison Result
+ ///
+ public ComparisonResult Result { get; private set; }
+ }
+}
diff --git a/FSI.Lib/FSI.Lib/CompareNetObjects/CompareExtensions.cs b/FSI.Lib/FSI.Lib/CompareNetObjects/CompareExtensions.cs
new file mode 100644
index 0000000..5850b94
--- /dev/null
+++ b/FSI.Lib/FSI.Lib/CompareNetObjects/CompareExtensions.cs
@@ -0,0 +1,71 @@
+using System;
+
+namespace FSI.Lib.CompareNetObjects
+{
+ ///
+ /// A set of BDD style comparison extensions for use with Testing Frameworks
+ ///
+ public static class CompareExtensions
+ {
+
+ static CompareExtensions()
+ {
+ Config = new ComparisonConfig();
+ }
+
+ ///
+ /// Alter the configuration for the comparison
+ ///
+ public static ComparisonConfig Config { get; set; }
+
+ ///
+ /// Throws a CompareException if the classes are not equal
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void ShouldCompare(this T actual, T expected, string message = null, ComparisonConfig compareConfig = null)
+ {
+ var logic = new CompareLogic(compareConfig ?? Config);
+ ComparisonResult result = logic.Compare(expected, actual);
+
+ if (!result.AreEqual)
+ {
+ throw new CompareException(result, BuildExpectedEqualMessage(message,result));
+ }
+ }
+
+ ///
+ /// Throws a CompareException if the classes are equal
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void ShouldNotCompare(this T actual, T expected, string message = null, ComparisonConfig compareConfig = null)
+ {
+ var logic = new CompareLogic(compareConfig ?? Config);
+ ComparisonResult result = logic.Compare(expected, actual);
+
+ if (result.AreEqual)
+ {
+ throw new CompareException(result, BuildExpectedNotEqualMessage(message, result));
+ }
+ }
+
+ private static string BuildExpectedEqualMessage(string message, ComparisonResult result)
+ {
+ message = message ?? "Objects expected to be equal";
+ return message + Environment.NewLine + result.DifferencesString + Environment.NewLine;
+ }
+
+ private static string BuildExpectedNotEqualMessage(string message, ComparisonResult result)
+ {
+ message = message ?? "Objects expected NOT to be equal";
+ return message + Environment.NewLine + result.DifferencesString + Environment.NewLine;
+ }
+ }
+}
diff --git a/FSI.Lib/FSI.Lib/CompareNetObjects/CompareLogic.cs b/FSI.Lib/FSI.Lib/CompareNetObjects/CompareLogic.cs
new file mode 100644
index 0000000..4ab46d2
--- /dev/null
+++ b/FSI.Lib/FSI.Lib/CompareNetObjects/CompareLogic.cs
@@ -0,0 +1,315 @@
+// This software is provided free of charge from Kellerman Software.
+// It may be used in any project, including commercial for sale projects.
+//
+// Check out our other great software at:
+// http://www.FSI.Lib.com
+// * Free Quick Reference Pack for Developers
+// * Free Sharp Zip Wrapper
+// * .NET Caching Library
+// * .NET Code Generator
+// * .NET Email Validation Library
+// * .NET Encryption Library
+// * .NET Excel Reports (Create Excel reports without excel being installed)
+// * .NET FTP Library
+// * .NET Link Tracker
+// * .NET Logging Library
+// * .NET PGP Library
+// * .NET SFTP Library
+// * .NET Word Reports (Create reports based on Microsoft Word files without having Microsoft Word installed)
+// * AccessDiff (Detects data, code, and form differences)
+// * Cheap Reports for Shopify
+// * Cheap Taxes for Shopify
+// * Connection String Creator
+// * Config Helper Pro (Read and write to the registry, config files, and INI files with 100% managed code)
+// * CSV Reports (CSV Reader, Writer)
+// * Easy Database Creator
+// * File Search Library
+// * Installerific (create Windows, Chocolatey, and Portable App installers)
+// * Knight Data Access Layer (ORM, LINQ Provider, Generator)
+// * Name Parser
+// * Ninja Database Pro (Object Relational database for .NET)
+// * Ninja Database Lite (Document database for .NET)
+// * Ninja WinRT Database (Object database for Windows 8 Runtime, Windows Phone 8)
+// * NUnit Test Generator
+// * Search Databases
+// * Source Code Search Tool
+// * Themed Winform Wizard
+// * Unused Stored Procedures
+// * User Agent Parser
+// * USPS Street Standardization Library
+// * What's Changed? (Compare words, strings, streams, and text files)
+
+#region Includes
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+#if !NETSTANDARD
+using System.Runtime.Serialization.Json;
+#endif
+
+#if !NETSTANDARD
+using FSI.Lib.CompareNetObjects.Properties;
+#endif
+
+#endregion
+
+#region License
+//Microsoft Public License (Ms-PL)
+
+//This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
+
+//1. Definitions
+
+//The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
+
+//A "contribution" is the original software, or any additions or changes to the software.
+
+//A "contributor" is any person that distributes its contribution under this license.
+
+//"Licensed patents" are a contributor's patent claims that read directly on its contribution.
+
+//2. Grant of Rights
+
+//(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
+
+//(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
+
+//3. Conditions and Limitations
+
+//(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
+
+//(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
+
+//(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
+
+//(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
+
+//(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
+#endregion
+
+namespace FSI.Lib.CompareNetObjects
+{
+ ///
+ /// Class that allows comparison of two objects of the same type to each other. Supports classes, lists, arrays, dictionaries, child comparison and more.
+ ///
+ ///
+ /// CompareLogic compareLogic = new CompareLogic();
+ ///
+ /// Person person1 = new Person();
+ /// person1.DateCreated = DateTime.Now;
+ /// person1.Name = "Greg";
+ ///
+ /// Person person2 = new Person();
+ /// person2.Name = "John";
+ /// person2.DateCreated = person1.DateCreated;
+ ///
+ /// ComparisonResult result = compareLogic.Compare(person1, person2);
+ ///
+ /// if (!result.AreEqual)
+ /// Console.WriteLine(result.DifferencesString);
+ ///
+ ///
+ public class CompareLogic : ICompareLogic
+ {
+ #region Class Variables
+
+ private ComparisonConfig _config;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// The default configuration
+ ///
+ public ComparisonConfig Config
+ {
+ get => _config;
+ set
+ {
+ var verifyConfig = new VerifyConfig();
+ verifyConfig.Verify(value);
+ _config = value;
+ }
+ }
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Set up defaults for the comparison
+ ///
+ public CompareLogic()
+ {
+ Config = new ComparisonConfig();
+ }
+
+ ///
+ /// Pass in the configuration
+ ///
+ ///
+ public CompareLogic(ComparisonConfig config)
+ {
+ Config = config;
+ }
+
+#if !NETSTANDARD
+
+ ///
+ /// Set up defaults for the comparison
+ ///
+ /// If true, use settings from the app.config
+ public CompareLogic(bool useAppConfigSettings)
+ {
+ Config = new ComparisonConfig();
+
+ if (useAppConfigSettings)
+ SetupWithAppConfigSettings();
+ }
+
+ private void SetupWithAppConfigSettings()
+ {
+ Config.MembersToIgnore = Settings.Default.MembersToIgnore == null
+ ? new List()
+ : new List((IEnumerable)Settings.Default.MembersToIgnore);
+
+ if (Settings.Default.MembersToIgnore != null)
+ {
+ foreach (var member in Settings.Default.MembersToIgnore)
+ {
+ Config.MembersToIgnore.Add(member);
+ }
+ }
+
+ Config.CompareStaticFields = Settings.Default.CompareStaticFields;
+ Config.CompareStaticProperties = Settings.Default.CompareStaticProperties;
+
+ Config.ComparePrivateProperties = Settings.Default.ComparePrivateProperties;
+ Config.ComparePrivateFields = Settings.Default.ComparePrivateFields;
+
+ Config.CompareChildren = Settings.Default.CompareChildren;
+ Config.CompareReadOnly = Settings.Default.CompareReadOnly;
+ Config.CompareFields = Settings.Default.CompareFields;
+ Config.IgnoreCollectionOrder = Settings.Default.IgnoreCollectionOrder;
+ Config.CompareProperties = Settings.Default.CompareProperties;
+ Config.Caching = Settings.Default.Caching;
+ Config.AutoClearCache = Settings.Default.AutoClearCache;
+ Config.MaxDifferences = Settings.Default.MaxDifferences;
+ Config.IgnoreUnknownObjectTypes = Settings.Default.IgnoreUnknownObjectTypes;
+ Config.IgnoreObjectDisposedException = Settings.Default.IgnoreObjectDisposedException;
+ }
+#endif
+
+ #endregion
+
+ #region Public Methods
+ ///
+ /// Compare two objects of the same type to each other.
+ ///
+ ///
+ /// Check the Differences or DifferencesString Properties for the differences.
+ /// Default MaxDifferences is 1 for performance
+ ///
+ /// The expected object value to compare
+ /// The actual object value to compare
+ /// True if they are equal
+ public ComparisonResult Compare(object expectedObject, object actualObject)
+ {
+ ComparisonResult result = new ComparisonResult(Config);
+
+ result.Watch.Start();
+
+ Config.PopulateHashSets();
+
+ RootComparer rootComparer = RootComparerFactory.GetRootComparer();
+
+ CompareParms parms = new CompareParms
+ {
+ Config = Config,
+ Result = result,
+ Object1 = expectedObject,
+ Object2 = actualObject,
+ BreadCrumb = string.Empty
+ };
+
+ rootComparer.Compare(parms);
+
+ if (Config.AutoClearCache)
+ ClearCache();
+
+ result.Watch.Stop();
+
+ return result;
+ }
+
+ ///
+ /// Reflection properties and fields are cached. By default this cache is cleared automatically after each compare.
+ ///
+ public void ClearCache()
+ {
+ Cache.ClearCache();
+ }
+
+#if !NETSTANDARD
+ ///
+ /// Save the current configuration to the passed stream
+ ///
+ ///
+ public void SaveConfiguration(Stream stream)
+ {
+ DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ComparisonConfig));
+ ser.WriteObject(stream, Config);
+
+ if (stream.CanSeek && stream.Position > 0)
+ stream.Seek(0, SeekOrigin.Begin);
+ }
+
+ ///
+ /// Load the current configuration from a json stream
+ ///
+ ///
+ public void LoadConfiguration(Stream stream)
+ {
+ if (stream.CanSeek && stream.Position > 0)
+ stream.Seek(0, SeekOrigin.Begin);
+
+ DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ComparisonConfig));
+ Config = (ComparisonConfig)ser.ReadObject(stream);
+ }
+#endif
+
+#if !NETSTANDARD
+ ///
+ /// Load the current configuration from a json stream
+ ///
+ ///
+ public void LoadConfiguration(string filePath)
+ {
+ using (FileStream stream = new FileStream(filePath, FileMode.Open))
+ {
+ DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ComparisonConfig));
+ Config = (ComparisonConfig)ser.ReadObject(stream);
+ Config.DifferenceCallback = d => { };
+ }
+ }
+
+ ///
+ /// Save the current configuration to a json file
+ ///
+ ///
+ public void SaveConfiguration(string filePath)
+ {
+ using (FileStream stream = new FileStream(filePath, FileMode.Create))
+ {
+ DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ComparisonConfig));
+ ser.WriteObject(stream, Config);
+ }
+ }
+#endif
+#endregion
+
+ }
+}
diff --git a/FSI.Lib/FSI.Lib/CompareNetObjects/CompareObjects.cs b/FSI.Lib/FSI.Lib/CompareNetObjects/CompareObjects.cs
new file mode 100644
index 0000000..5b2bc12
--- /dev/null
+++ b/FSI.Lib/FSI.Lib/CompareNetObjects/CompareObjects.cs
@@ -0,0 +1,358 @@
+//Provided for backward compatibility from 1.7.4
+
+#region Includes
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+#endregion
+
+namespace FSI.Lib.CompareNetObjects
+{
+ ///
+ /// Obsolete Use CompareLogic instead
+ ///
+ [Obsolete("Use CompareLogic instead", true)]
+ public class CompareObjects
+ {
+ #region Class Variables
+
+ private readonly CompareLogic _logic;
+ private ComparisonResult _result;
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Obsolete Use CompareLogic instead
+ ///
+ [Obsolete("Use CompareLogic instead", true)]
+ public CompareObjects()
+ {
+ _logic = new CompareLogic();
+ _result = new ComparisonResult(_logic.Config);
+ }
+
+#if !NETSTANDARD
+ ///
+ /// Obsolete Use CompareLogic instead
+ ///
+ [Obsolete("Use CompareLogic instead", true)]
+ public CompareObjects(bool useAppConfigSettings)
+ {
+ _logic = new CompareLogic(useAppConfigSettings);
+ _result = new ComparisonResult(_logic.Config);
+ }
+#endif
+
+ #endregion
+
+ #region Properties
+
+
+#if !NETSTANDARD
+ ///
+ /// Obsolete Use the ComparisonResult.ElapsedMilliseconds returned from CompareLogic.Compare
+ ///
+ [Obsolete("Use the ComparisonResult.ElapsedMilliseconds returned from CompareLogic.Compare", true)]
+ public long ElapsedMilliseconds
+ {
+ get { return _result.ElapsedMilliseconds; }
+ }
+#endif
+
+ ///
+ /// Obsolete Use CompareLogic.Config.ShowBreadcrumb instead
+ ///
+ [Obsolete("Use CompareLogic.Config.ShowBreadcrumb instead", true)]
+ public bool ShowBreadcrumb
+ {
+ get { return _logic.Config.ShowBreadcrumb; }
+ set { _logic.Config.ShowBreadcrumb = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.MembersToIgnore for members or CompareLogic.Config.ClassTypesToIgnore instead
+ ///
+ [Obsolete("Use CompareLogic.Config.MembersToIgnore for members or CompareLogic.Config.ClassTypesToIgnore instead", true)]
+ public List ElementsToIgnore
+ {
+ get { return _logic.Config.MembersToIgnore.ToList(); }
+ set
+ {
+ _logic.Config.MembersToIgnore.Clear();
+ foreach (var item in value)
+ {
+ _logic.Config.MembersToIgnore.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.MembersToInclude or CompareLogic.Config.ClassTypesToInclude instead
+ ///
+ [Obsolete("Use CompareLogic.Config.MembersToInclude or CompareLogic.Config.ClassTypesToInclude instead", true)]
+ public List ElementsToInclude
+ {
+ get { return _logic.Config.MembersToInclude.ToList(); }
+ set
+ {
+ _logic.Config.MembersToInclude.Clear();
+ foreach (var item in value)
+ {
+ _logic.Config.MembersToInclude.Add(item);
+ }
+ }
+ }
+
+ //Security restriction in Silverlight prevents getting private properties and fields
+#if !NETSTANDARD
+
+ ///
+ /// Obsolete Use CompareLogic.Config.ComparePrivateProperties instead
+ ///
+ [Obsolete("Use CompareLogic.Config.ComparePrivateProperties instead", true)]
+ public bool ComparePrivateProperties
+ {
+ get { return _logic.Config.ComparePrivateProperties; }
+ set { _logic.Config.ComparePrivateProperties = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.ComparePrivateFields instead
+ ///
+ [Obsolete("Use CompareLogic.Config.ComparePrivateFields instead", true)]
+ public bool ComparePrivateFields
+ {
+ get { return _logic.Config.ComparePrivateFields; }
+ set { _logic.Config.ComparePrivateFields = value; }
+ }
+#endif
+
+ ///
+ /// Obsolete Use CompareLogic.Config.CompareStaticProperties instead
+ ///
+ [Obsolete("Use CompareLogic.Config.CompareStaticProperties instead", true)]
+ public bool CompareStaticProperties
+ {
+ get { return _logic.Config.CompareStaticProperties; }
+ set { _logic.Config.CompareStaticProperties = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.CompareStaticFields instead
+ ///
+ [Obsolete("Use CompareLogic.Config.CompareStaticFields instead", true)]
+ public bool CompareStaticFields
+ {
+ get { return _logic.Config.CompareStaticFields; }
+ set { _logic.Config.CompareStaticFields = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.CompareChildren instead
+ ///
+ [Obsolete("Use CompareLogic.Config.CompareChildren instead", true)]
+ public bool CompareChildren
+ {
+ get { return _logic.Config.CompareChildren; }
+ set { _logic.Config.CompareChildren = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.CompareReadOnly instead
+ ///
+ [Obsolete("Use CompareLogic.Config.CompareReadOnly instead", true)]
+ public bool CompareReadOnly
+ {
+ get { return _logic.Config.CompareReadOnly; }
+ set { _logic.Config.CompareReadOnly = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.CompareFields instead
+ ///
+ [Obsolete("Use CompareLogic.Config.CompareFields instead", true)]
+ public bool CompareFields
+ {
+ get { return _logic.Config.CompareFields; }
+ set { _logic.Config.CompareFields = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.IgnoreCollectionOrder instead
+ ///
+ [Obsolete("Use CompareLogic.Config.IgnoreCollectionOrder instead", true)]
+ public bool IgnoreCollectionOrder
+ {
+ get { return _logic.Config.IgnoreCollectionOrder; }
+ set { _logic.Config.IgnoreCollectionOrder = value; }
+ }
+
+
+ ///
+ /// Obsolete Use CompareLogic.Config.CompareProperties instead
+ ///
+ [Obsolete("Use CompareLogic.Config.CompareProperties instead", true)]
+ public bool CompareProperties
+ {
+ get { return _logic.Config.CompareProperties; }
+ set { _logic.Config.CompareProperties = value; }
+ }
+
+
+ ///
+ /// Obsolete Use CompareLogic.Config.MaxDifferences instead
+ ///
+ [Obsolete("Use CompareLogic.Config.MaxDifferences instead", true)]
+ public int MaxDifferences
+ {
+ get { return _logic.Config.MaxDifferences; }
+ set { _logic.Config.MaxDifferences = value; }
+ }
+
+ ///
+ /// Obsolete Use the ComparisonResult.Differences returned from CompareLogic.Compare
+ ///
+ [Obsolete("Use the ComparisonResult.Differences returned from CompareLogic.Compare", true)]
+ public List Differences
+ {
+ get { return _result.Differences; }
+ set { _result.Differences = value; }
+ }
+
+ ///
+ /// Obsolete Use the ComparisonResult.DifferencesString returned from CompareLogic.Compare
+ ///
+ [Obsolete("Use the ComparisonResult.DifferencesString returned from CompareLogic.Compare", true)]
+ public string DifferencesString
+ {
+ get { return _result.DifferencesString; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.AutoClearCache instead
+ ///
+ [Obsolete("Use CompareLogic.Config.AutoClearCache instead", true)]
+ public bool AutoClearCache
+ {
+ get { return _logic.Config.AutoClearCache; }
+ set { _logic.Config.AutoClearCache = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.Caching instead
+ ///
+ [Obsolete("Use CompareLogic.Config.Caching instead", true)]
+ public bool Caching
+ {
+ get { return _logic.Config.Caching; }
+ set { _logic.Config.Caching = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.AttributesToIgnore instead
+ ///
+ [Obsolete("Use CompareLogic.Config.AttributesToIgnore instead", true)]
+ public List AttributesToIgnore
+ {
+ get { return _logic.Config.AttributesToIgnore.ToList(); }
+ set
+ {
+ _logic.Config.AttributesToIgnore.Clear();
+ foreach (var item in value)
+ {
+ _logic.Config.AttributesToIgnore.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.IgnoreObjectTypes instead
+ ///
+ [Obsolete("Use CompareLogic.Config.IgnoreObjectTypes instead", true)]
+ public bool IgnoreObjectTypes
+ {
+ get { return _logic.Config.IgnoreObjectTypes; }
+ set { _logic.Config.IgnoreObjectTypes = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.CustomComparers instead
+ ///
+ [Obsolete("Use CompareLogic.Config.CustomComparers", true)]
+ public Func IsUseCustomTypeComparer { get; set; }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.CustomComparers instead
+ ///
+ [Obsolete("Use CompareLogic.Config.CustomComparers", true)]
+ public Action CustomComparer { get; set; }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.ExpectedName instead
+ ///
+ [Obsolete("Use CompareLogic.Config.ExpectedName instead", true)]
+ public string ExpectedName
+ {
+ get { return _logic.Config.ExpectedName; }
+ set { _logic.Config.ExpectedName = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.ActualName instead
+ ///
+ [Obsolete("Use CompareLogic.Config.ActualName instead", true)]
+ public string ActualName
+ {
+ get { return _logic.Config.ActualName; }
+ set { _logic.Config.ActualName = value; }
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.Config.DifferenceCallback instead
+ ///
+ [Obsolete("Use CompareLogic.Config.DifferenceCallback instead", true)]
+ public Action DifferenceCallback
+ {
+ get { return _logic.Config.DifferenceCallback; }
+ set { _logic.Config.DifferenceCallback = value; }
+ }
+
+
+ ///
+ /// Obsolete Use CompareLogic.Config.CollectionMatchingSpec instead
+ ///
+ [Obsolete("Use CompareLogic.Config.CollectionMatchingSpec instead", true)]
+ public Dictionary> CollectionMatchingSpec
+ {
+ get { return _logic.Config.CollectionMatchingSpec; }
+ set { _logic.Config.CollectionMatchingSpec = value; }
+ }
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Obsolete Use CompareLogic.Compare instead
+ ///
+ [Obsolete("Use CompareLogic.Compare instead", true)]
+ public bool Compare(object object1, object object2)
+ {
+ _result = _logic.Compare(object1, object2);
+
+ return _result.AreEqual;
+ }
+
+ ///
+ /// Obsolete Use CompareLogic.ClearCache instead
+ ///
+ [Obsolete("Use CompareLogic.ClearCache instead", true)]
+ public void ClearCache()
+ {
+ _logic.ClearCache();
+ }
+
+ #endregion
+ }
+}
diff --git a/FSI.Lib/FSI.Lib/CompareNetObjects/CompareParms.cs b/FSI.Lib/FSI.Lib/CompareNetObjects/CompareParms.cs
new file mode 100644
index 0000000..9949d72
--- /dev/null
+++ b/FSI.Lib/FSI.Lib/CompareNetObjects/CompareParms.cs
@@ -0,0 +1,71 @@
+using System;
+using FSI.Lib.CompareNetObjects.TypeComparers;
+
+namespace FSI.Lib.CompareNetObjects
+{
+ ///
+ /// Compare Parameters
+ ///
+ public class CompareParms
+ {
+ ///
+ /// The configuration settings
+ ///
+ public ComparisonConfig Config { get; set; }
+
+ ///
+ /// The type of the first object
+ ///
+ public Type Object1Type { get; set; }
+
+ ///
+ /// The type of the second object
+ ///
+ public Type Object2Type { get; set; }
+
+ ///
+ /// The declared type of the first object in its parent. e.g. IList
+ ///
+ public Type Object1DeclaredType { get; set; }
+
+ ///
+ /// The declared type of the second object in its parent. e.g. IList
+ ///
+ public Type Object2DeclaredType { get; set; }
+
+ ///
+ /// Details about the comparison
+ ///
+ public ComparisonResult Result { get; set; }
+
+ ///
+ /// A reference to the parent object1
+ ///
+ public object ParentObject1 { get; set; }
+
+ ///
+ /// A reference to the parent object2
+ ///
+ public object ParentObject2 { get; set; }
+
+ ///
+ /// The first object to be compared
+ ///
+ public object Object1 { get; set; }
+
+ ///
+ /// The second object to be compared
+ ///
+ public object Object2 { get; set; }
+
+ ///
+ /// The breadcrumb in the tree
+ ///
+ public string BreadCrumb { get; set; }
+
+ ///
+ /// Custom comparer used to assert Object1
+ ///
+ public BaseTypeComparer CustomPropertyComparer { get; set; }
+ }
+}
diff --git a/FSI.Lib/FSI.Lib/CompareNetObjects/ComparisonConfig.cs b/FSI.Lib/FSI.Lib/CompareNetObjects/ComparisonConfig.cs
new file mode 100644
index 0000000..0374dfb
--- /dev/null
+++ b/FSI.Lib/FSI.Lib/CompareNetObjects/ComparisonConfig.cs
@@ -0,0 +1,650 @@
+using System;
+using System.Collections.Generic;
+using FSI.Lib.CompareNetObjects.TypeComparers;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+#if !NETSTANDARD
+using System.Runtime.Serialization;
+#endif
+
+namespace FSI.Lib.CompareNetObjects
+{
+ ///
+ /// Configuration
+ ///
+#if !NETSTANDARD
+ [DataContract]
+#endif
+ public class ComparisonConfig
+ {
+ #region Class Variables
+ private Action _differenceCallback;
+ private int _maxStructDepth;
+ #endregion
+
+ #region Constructors
+ ///
+ /// Default Constructor
+ ///
+ public ComparisonConfig()
+ {
+ Reset();
+ }
+ #endregion
+
+ #region Properties
+
+ internal HashSet AttributesToIgnoreSet { get; set; }
+ internal HashSet MembersToIgnoreSet { get; set; }
+ internal HashSet MembersToIncludeSet { get; set; }
+ internal HashSet ClassTypesToIgnoreSet { get; set; }
+ internal HashSet ClassTypesToIncludeSet { get; set; }
+ internal HashSet TypesToIgnoreSet { get; set; }
+ internal HashSet TypesToIncludeSet { get; set; }
+ internal HashSet RequiredAttributesToCompareSet { get; set; }
+
+ ///
+ /// By default Compare .NET Objects uses reference equal to identify objects.
+ /// Versions 4.61 and older used the hash code. Setting this to true will identify objects by hash code instead of reference equals.
+ /// The default is false
+ ///
+ public bool UseHashCodeIdentifier { get; set; }
+
+ ///
+ /// When comparing strings or StringBuilder types, perform a case sensitive comparison. The default is true.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool CaseSensitive { get; set; }
+
+ ///
+ /// Ignore exceptions when objects are disposed
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool IgnoreObjectDisposedException { get; set; }
+
+ ///
+ /// Ignore millisecond differences between DateTime values or DateTimeOffset values. The default is 0 (any time difference will be shown).
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public int MaxMillisecondsDateDifference { get; set; }
+
+ ///
+ /// When comparing DateTimeOffsets, offsets will be compared as well as the UtcDateTimes. The default is false.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool CompareDateTimeOffsetWithOffsets { get; set; }
+
+
+ ///
+ /// When comparing DateTimeOffsets, timezone difference will be ignored by changing both object to their UTC equivalent value. The default is false.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool IgnoreDateTimeOffsetTimezones { get; set; }
+
+ ///
+ /// When comparing struct, the depth to compare for children. The default is 2, the max is 5
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public int MaxStructDepth
+ {
+ get { return _maxStructDepth; }
+ set
+ {
+ if (value < 1 || value > 5)
+ {
+ throw new ArgumentOutOfRangeException("MaxStructDepth", "Cannot be less than 1 or greater than 5");
+ }
+
+ _maxStructDepth = value;
+ }
+ }
+
+ ///
+ /// If true, unknown object types will be ignored instead of throwing an exception. The default is false.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool IgnoreUnknownObjectTypes { get; set; }
+
+ ///
+ /// If true, invalid indexers will be skipped. The default is false.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool SkipInvalidIndexers { get; set; }
+
+ ///
+ /// If a class implements an interface then only members of the interface will be compared. The default is all members are compared.
+ ///
+ public List InterfaceMembers { get; set; }
+#if !NETSTANDARD
+ [DataMember(Name = "InterfaceMembers")]
+ private List InterfaceMembersSerializer
+ {
+ get { return TypeHelper.ListOfTypesSerializer(InterfaceMembers);}
+ set { InterfaceMembers = TypeHelper.ListOfTypesDeserializer(value); }
+ }
+#endif
+
+
+
+ ///
+ /// Show breadcrumb at each stage of the comparision. The default is false.
+ /// This is useful for debugging deep object graphs.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool ShowBreadcrumb { get; set; }
+
+ ///
+ /// A list of class types to be ignored in the comparison. The default is to compare all class types.
+ ///
+ public List ClassTypesToIgnore { get; set; }
+#if !NETSTANDARD
+ [DataMember(Name = "ClassTypesToIgnore")]
+ private List ClassTypesToIgnoreSerializer
+ {
+ get { return TypeHelper.ListOfTypesSerializer(ClassTypesToIgnore); }
+ set { ClassTypesToIgnore = TypeHelper.ListOfTypesDeserializer(value); }
+ }
+#endif
+
+ ///
+ /// Only these class types will be compared. The default is to compare all class types.
+ ///
+ /// If you specify a class type here no other class types will be compared unless it is in this list.
+ public List ClassTypesToInclude { get; set; }
+#if !NETSTANDARD
+ [DataMember(Name = "ClassTypesToInclude")]
+ private List ClassTypesToIncludeSerializer
+ {
+ get { return TypeHelper.ListOfTypesSerializer(ClassTypesToInclude); }
+ set { ClassTypesToInclude = TypeHelper.ListOfTypesDeserializer(value); }
+ }
+#endif
+
+ ///
+ /// A list of types to be ignored in the comparison. The default is to compare all types. A typical thing to not compare are GUIDs
+ ///
+ public List TypesToIgnore { get; set; }
+#if !NETSTANDARD
+ [DataMember(Name = "TypesToIgnore")]
+ private List TypesToIgnoreSerializer
+ {
+ get { return TypeHelper.ListOfTypesSerializer(TypesToIgnore); }
+ set { TypesToIgnore = TypeHelper.ListOfTypesDeserializer(value); }
+ }
+#endif
+
+ ///
+ /// Only these types will be compared. The default is to compare all types.
+ ///
+ /// If you specify a type here no others will be compared unless it is in this list. You must specify ALL Types that you want to compare.
+ public List TypesToInclude { get; set; }
+#if !NETSTANDARD
+ [DataMember(Name = "TypesToInclude")]
+ private List TypesToIncludeSerializer
+ {
+ get { return TypeHelper.ListOfTypesSerializer(TypesToInclude); }
+ set { TypesToInclude = TypeHelper.ListOfTypesDeserializer(value); }
+ }
+#endif
+
+ ///
+ /// Ignore Data Table Names, Data Table Column Names, properties, or fields by name during the comparison. Case sensitive. The default is to compare all members.
+ ///
+ /// MembersToIgnore.Add("CreditCardNumber");
+ /// MembersToIgnore.Add("Invoice.InvoiceGuid");
+ /// MembersToIgnore.Add("*Id");
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public List MembersToIgnore { get ; set; }
+
+ ///
+ /// Ignore property during the comparison. Property is specific to the generic type.
+ ///
+ ///
+ ///
+ ///
+ /// IgnoreProperty<Person>(x => x.Name)
+ public void IgnoreProperty(Expression> ignoredProperty)
+ {
+ LambdaExpression lambda = ignoredProperty;
+ MemberExpression memberExpression;
+
+ if (lambda.Body is UnaryExpression unaryExpression)
+ {
+ memberExpression = unaryExpression.Operand as MemberExpression ??
+ // catches methods, maybe other things
+ throw new ArgumentException(
+ $"IgnoreProperty can only be used with properties. {ignoredProperty} is not a property.");
+ }
+ else
+ {
+ memberExpression = (MemberExpression) lambda.Body;
+ }
+
+ var propInfo = memberExpression.Member as PropertyInfo;
+ if (propInfo == null)
+ // catches fields, maybe other things
+ {
+ throw new ArgumentException($"IgnoreProperty can only be used with properties. {ignoredProperty} is not a property.");
+ }
+
+ var name = propInfo.Name;
+ MembersToIgnore.Add(typeof(TClass).Name + "." + name);
+ }
+
+ ///
+ /// Define a Custom Property Comparer using a lambda expression
+ ///
+ ///
+ ///
+ ///
+ public void CustomPropertyComparer(Expression> customProperty, BaseTypeComparer validator)
+ {
+ LambdaExpression lambda = customProperty;
+ MemberExpression memberExpression;
+
+ if (lambda.Body is UnaryExpression unaryExpression)
+ {
+ memberExpression = unaryExpression.Operand as MemberExpression ??
+ // catches methods, maybe other things
+ throw new ArgumentException(
+ $"Custom property comparer can only be used with properties. {customProperty} is not a property.");
+ }
+ else
+ {
+ memberExpression = (MemberExpression)lambda.Body;
+ }
+
+ var propInfo = memberExpression.Member as PropertyInfo;
+ if (propInfo == null)
+ // catches fields, maybe other things
+ {
+ throw new ArgumentException($"Custom property comparer can only be used with properties. {customProperty} is not a property.");
+ }
+
+ var name = propInfo.Name;
+ CustomPropertyComparers.Add(typeof(TClass).Name + "." + name, validator);
+ }
+
+ ///
+ /// Only compare elements by name for Data Table Names, Data Table Column Names, properties and fields. Case sensitive. The default is to compare all members.
+ ///
+ /// MembersToInclude.Add("FirstName")
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public List MembersToInclude { get; set; }
+
+#if !NETSTANDARD1_3
+ ///
+ /// If true, private properties and fields will be compared. The default is false. Silverlight and WinRT restricts access to private variables.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool ComparePrivateProperties { get; set; }
+#endif
+
+#if !NETSTANDARD1_3
+ ///
+ /// If true, private fields will be compared. The default is false. Silverlight and WinRT restricts access to private variables.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool ComparePrivateFields { get; set; }
+#endif
+
+ ///
+ /// If true, static properties will be compared. The default is true.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool CompareStaticProperties { get; set; }
+
+ ///
+ /// If true, static fields will be compared. The default is true.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool CompareStaticFields { get; set; }
+
+ ///
+ /// If true, child objects will be compared. The default is true.
+ /// If false, and a list or array is compared list items will be compared but not their children.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool CompareChildren { get; set; }
+
+ ///
+ /// If true, compare read only properties (only the getter is implemented). The default is true.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool CompareReadOnly { get; set; }
+
+ ///
+ /// If true, compare fields of a class (see also CompareProperties). The default is true.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool CompareFields { get; set; }
+
+ ///
+ /// If true, compare each item within a collection to every item in the other. The default is false. WARNING: setting this to true significantly impacts performance.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool IgnoreCollectionOrder { get; set; }
+
+ ///
+ /// If true, compare properties of a class (see also CompareFields). The default is true.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool CompareProperties { get; set; }
+
+ ///
+ /// The maximum number of differences to detect. The default is 1 for performance reasons.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public int MaxDifferences { get; set; }
+
+ ///
+ /// The maximum number of differences to detect when comparing byte arrays. The default is 1.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public int MaxByteArrayDifferences { get; set; }
+
+ ///
+ /// Reflection properties and fields are cached. By default this cache is cleared after each compare. Set to false to keep the cache for multiple compares.
+ ///
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool AutoClearCache { get; set; }
+
+ ///
+ /// By default properties and fields for types are cached for each compare. By default this cache is cleared after each compare.
+ ///
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool Caching { get; set; }
+
+ ///
+ /// A list of attributes to ignore a class, property or field
+ ///
+ /// AttributesToIgnore.Add(typeof(XmlIgnoreAttribute));
+ public List AttributesToIgnore { get; set; }
+#if !NETSTANDARD
+ [DataMember(Name = "AttributesToIgnore")]
+ private List AttributesToIgnoreSerializer
+ {
+ get { return TypeHelper.ListOfTypesSerializer(AttributesToIgnore); }
+ set { AttributesToIgnore = TypeHelper.ListOfTypesDeserializer(value); }
+ }
+#endif
+
+ ///
+ /// If a property or field don't have at least one of the attributes in this list, it will be ignored
+ ///
+ /// RequiredAttributesToCompare.Add(typeof(XmlIgnoreAttribute));
+ public List RequiredAttributesToCompare { get; set; }
+#if !NETSTANDARD
+ [DataMember(Name = "RequiredAttributesToCompare")]
+ private List RequiredAttributesToCompareSerializer
+ {
+ get { return TypeHelper.ListOfTypesSerializer(RequiredAttributesToCompare); }
+ set { RequiredAttributesToCompare = TypeHelper.ListOfTypesDeserializer(value); }
+ }
+#endif
+
+ ///
+ /// If true, objects will be compared ignore their type diferences. The default is false.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool IgnoreObjectTypes { get; set; }
+
+ ///
+ /// In the differences string, this is the name for expected name. The default is: Expected
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public string ExpectedName { get; set; }
+
+ ///
+ /// In the differences string, this is the name for the actual name. The default is: Actual
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public string ActualName { get; set; }
+
+ ///
+ /// Callback invoked each time the comparer finds a difference. The default is no call back.
+ ///
+ public Action DifferenceCallback
+ {
+ get { return _differenceCallback; }
+ set
+ {
+ if (null != value)
+ {
+ _differenceCallback = value;
+ }
+ }
+ }
+
+ ///
+ /// This property is used when IgnoreCollectionOrder is set to true, otherwise it has no effect.
+ /// Sometimes one wants to match items between collections by some key first, and then
+ /// compare the matched objects. Without this, the comparer basically says there is no
+ /// match in collection B for any given item in collection A that doesn't Compare with a result of true.
+ /// The results of this aren't particularly useful for object graphs that are mostly the same, but not quite.
+ /// Enter CollectionMatchingSpec
+ ///
+ /// The enumerable strings should be property (not field, for now, to keep it simple) names of the
+ /// Type when encountered that will be used for matching
+ ///
+ /// You can use complex type properties, too, as part of the key to match. To match on all props/fields on
+ /// such a matching key, Don't set this property (default comparer behavior)
+ /// NOTE: types are looked up as exact. e.g. if foo is an entry in the dictionary and bar is a
+ /// sub-class of foo, upon encountering a bar type, the comparer will not find the entry of foo
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public Dictionary> CollectionMatchingSpec { get; set; }
+
+ ///
+ /// A list of custom comparers that take priority over the built in comparers
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public List CustomComparers { get; set; }
+
+
+ ///
+ /// A list of custom property comparers that take priority over the built in and type comparers
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public Dictionary CustomPropertyComparers { get; set; }
+
+ ///
+ /// If true, string.empty and null will be treated as equal for Strings and String Builder. The default is false.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool TreatStringEmptyAndNullTheSame { get; set; }
+
+ ///
+ /// If true, leading and trailing whitespaces will be ignored for Strings and String Builder. The default is false.
+ ///
+#if !DNCORE
+ [DataMember]
+#endif
+ public bool IgnoreStringLeadingTrailingWhitespace { get; set; }
+
+ ///
+ /// The precision to compare double values. The default is 0.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public double DoublePrecision { get; set; }
+
+ ///
+ /// The precision to compare decimal values. The default is 0.
+ ///
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public decimal DecimalPrecision { get; set; }
+
+#if !NETSTANDARD
+ [DataMember]
+#endif
+ public bool IgnoreConcreteTypes { get; set; }
+
+ ///
+ /// If true, properties that are defined in the actual object but missing in the expected object will not be flagged as differences. Default is true.
+ ///
+ public bool IgnoreMissingProperties { get; set; }
+
+ ///
+ /// If true, fields that are defined in the actual object but missing in the expected object will not be flagged as differences. Default is true.
+ ///
+ public bool IgnoreMissingFields { get; set; }
+
+ #endregion
+
+ #region Methods
+
+ //These hash sets are used for performance
+ internal void PopulateHashSets()
+ {
+ AttributesToIgnoreSet = new HashSet((AttributesToIgnore ?? new List()).Distinct());
+ MembersToIgnoreSet = new HashSet((MembersToIgnore ?? new List()).Distinct());
+ MembersToIncludeSet = new HashSet((MembersToInclude ?? new List()).Distinct());
+ ClassTypesToIgnoreSet = new HashSet((ClassTypesToIgnore ?? new List()).Distinct());
+ ClassTypesToIncludeSet = new HashSet((ClassTypesToInclude ?? new List()).Distinct());
+ TypesToIgnoreSet = new HashSet((TypesToIgnore ?? new List()).Distinct());
+ TypesToIncludeSet = new HashSet((TypesToInclude ?? new List()).Distinct());
+ RequiredAttributesToCompareSet = new HashSet((RequiredAttributesToCompare ?? new List()).Distinct());
+ }
+
+ ///
+ /// Backing member that supports
+ ///
+ private bool? _hasWildcardInMembersToIgnore;
+
+ ///
+ /// Computed value of whether or not exclusion list has wildcards.
+ ///
+ public bool HasWildcardMembersToExclude()
+ {
+ if (_hasWildcardInMembersToIgnore.HasValue)
+ {
+ return _hasWildcardInMembersToIgnore.Value;
+ }
+
+ _hasWildcardInMembersToIgnore = MembersToIgnoreSet.Any(x => x.IndexOf("*") > -1);
+ return _hasWildcardInMembersToIgnore.Value;
+ }
+
+ ///
+ /// Reset the configuration to the default values
+ ///
+ public void Reset()
+ {
+ AttributesToIgnore = new List();
+ RequiredAttributesToCompare = new List();
+ _differenceCallback = d => { };
+
+ MembersToIgnore = new List();
+ _hasWildcardInMembersToIgnore = null;
+
+ MembersToInclude = new List();
+ ClassTypesToIgnore = new List();
+ ClassTypesToInclude = new List();
+ TypesToIgnore = new List();
+ TypesToInclude = new List();
+
+ CompareStaticFields = true;
+ CompareStaticProperties = true;
+#if !NETSTANDARD1_3
+ ComparePrivateProperties = false;
+ ComparePrivateFields = false;
+#endif
+ CustomPropertyComparers = new Dictionary();
+ CompareChildren = true;
+ CompareReadOnly = true;
+ CompareFields = true;
+ CompareDateTimeOffsetWithOffsets = false;
+ IgnoreCollectionOrder = false;
+ CompareProperties = true;
+ Caching = true;
+ AutoClearCache = true;
+ IgnoreObjectTypes = false;
+ MaxDifferences = 1;
+ ExpectedName = "Expected";
+ ActualName = "Actual";
+ CustomComparers = new List();
+ TreatStringEmptyAndNullTheSame = false;
+ InterfaceMembers = new List();
+ SkipInvalidIndexers = false;
+ MaxByteArrayDifferences = 1;
+ CollectionMatchingSpec = new Dictionary>();
+ IgnoreUnknownObjectTypes = false;
+ MaxStructDepth = 2;
+ CaseSensitive = true;
+ IgnoreStringLeadingTrailingWhitespace = false;
+ IgnoreMissingProperties = true;
+ IgnoreMissingFields = true;
+ }
+#endregion
+ }
+}
diff --git a/FSI.Lib/FSI.Lib/CompareNetObjects/ComparisonResult.cs b/FSI.Lib/FSI.Lib/CompareNetObjects/ComparisonResult.cs
new file mode 100644
index 0000000..afebc95
--- /dev/null
+++ b/FSI.Lib/FSI.Lib/CompareNetObjects/ComparisonResult.cs
@@ -0,0 +1,245 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+
+namespace FSI.Lib.CompareNetObjects
+{
+ ///
+ /// Details about the comparison
+ ///
+ public class ComparisonResult
+ {
+ #region Class Variables
+ private string _differencesString;
+ ///
+ /// Keep track of parent objects in the object hierarchy by using reference equals
+ ///
+ private readonly Dictionary