IGraphComparer
IGraphComparer is interface for comparers that can handle cyclic object graphs.
/// <summary>Object graph aware comparer. Detects cycles.</summary>
public interface IGraphComparer
{
/// <summary>Store for context when transitioning between non-graph and graph comparers.</summary>
static ThreadLocal<IGraphComparerContext2?> context2 = new();
/// <summary>Store for context when transitioning between non-graph and graph comparers.</summary>
public static ThreadLocal<IGraphComparerContext2?> Context2 => context2;
/// <summary>Is comparer structure cyclical.</summary>
bool IsCyclical { get; set; }
/// <summary>Compares order of <paramref name="x"/> to <paramref name="y"/>.</summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="context">Graph traverse context</param>
/// <returns>
/// Signed interger that indicates relative value of <paramref name="x"/> to <paramref name="y"/>.
/// <![CDATA[<0]]> for <paramref name="x"/> preceding <paramref name="y"/>.
/// <![CDATA[>0]]> for <paramref name="x"/> trailing <paramref name="y"/>.
/// <![CDATA[0]]> for <paramref name="x"/> being equal to <paramref name="y"/>.
/// </returns>
/// <remarks>
/// Implementation may use <see cref="Context2"/> when
/// visiting non-graph-supported sub-comparer.
/// </remarks>
int Compare(object? x, object? y, IGraphComparerContext2 context);
}
IGraphComparer<T> uses strong type generics.
/// <summary>Object graph aware comparer. Detects cycles.</summary>
/// <remarks>
/// Implementation may be start node distinctive or agnostic.
/// If former, then implementation typically uses order specific hashing, e.g. FNV.
/// If later, then uses add or xor hashing between objects.
/// </remarks>
public interface IGraphComparer<in T>
{
/// <summary>Is comparer structure cyclical.</summary>
bool IsCyclical { get; set; }
/// <summary>Compares order of <paramref name="x"/> to <paramref name="y"/>.</summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="context">Graph traverse context</param>
/// <returns>
/// Signed interger that indicates relative value of <paramref name="x"/> to <paramref name="y"/>.
/// <![CDATA[<0]]> for <paramref name="x"/> preceding <paramref name="y"/>.
/// <![CDATA[>0]]> for <paramref name="x"/> trailing <paramref name="y"/>.
/// <![CDATA[0]]> for <paramref name="x"/> being equal to <paramref name="y"/>.
/// </returns>
/// <remarks>
/// Implementation may use <see cref="IGraphComparer.Context2"/> when visiting
/// non-graph-supported sub-comparer.
/// </remarks>
int Compare(T? x, T? y, IGraphComparerContext2 context);
}
IGraphEqualityComparer
IGraphEqualityComparer is interface for comparers that can handle cyclic object graphs.
/// <summary>Object graph aware equality comparer. Detects cycles.</summary>
public interface IGraphEqualityComparer
{
/// <summary>Store for context when transitioning between non-graph and graph comparers.</summary>
static ThreadLocal<IGraphComparerContext?> context = new();
/// <summary>Store for context when transitioning between non-graph and graph comparers.</summary>
static ThreadLocal<IGraphComparerContext2?> context2 = new();
/// <summary>Store for context when transitioning between non-graph and graph comparers.</summary>
public static ThreadLocal<IGraphComparerContext?> Context => context;
/// <summary>Store for context when transitioning between non-graph and graph comparers.</summary>
public static ThreadLocal<IGraphComparerContext2?> Context2 => context2;
/// <summary>Is comparer structure cyclical.</summary>
bool IsCyclical { get; set; }
/// <summary>Compares for equality of <paramref name="x"/> and <paramref name="y"/>. Detects object cycles.</summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="context">Compare evaluation context.</param>
/// <returns></returns>
/// <remarks>Implementation may use <see cref="Context2"/> when visiting non-graph-supported sub-comparer.</remarks>
bool Equals(object? x, object? y, IGraphComparerContext2 context);
/// <summary>Return a hash code for <paramref name="obj"/>.</summary>
/// <param name="obj">Object to hash</param>
/// <param name="context">Hashing context</param>
/// <remarks>Implementation may use <see cref="Context"/> when visiting non-graph-supported sub-comparer.</remarks>
int GetHashCode([DisallowNull] object obj, IGraphComparerContext context);
}
IGraphEqualityComparer<T> uses strong type generics.
/// <summary>Object graph aware equality comparer. Detects cycles.</summary>
/// <remarks>
/// Implementation may be start node distinctive or agnostic.
/// If former, then implementation typically uses order specific hashing, e.g. FNV.
/// If later, then uses add or xor hashing between objects.
/// </remarks>
public interface IGraphEqualityComparer<in T>
{
/// <summary>Is comparer structure cyclical.</summary>
bool IsCyclical { get; set; }
/// <summary>
/// Compares for equality of <paramref name="x"/> and <paramref name="y"/>.
/// Detects object cycles.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="context">Compare evaluation context.</param>
/// <returns></returns>
/// <remarks>
/// Implementation may use <see cref="IGraphEqualityComparer.Context2"/>
/// when visiting non-graph-supported sub-comparer.
/// </remarks>
bool Equals(T? x, T? y, IGraphComparerContext2 context);
/// <summary>Return a hash code for <paramref name="obj"/>.</summary>
/// <param name="obj">Object to hash</param>
/// <param name="context">Hashing context</param>
/// <remarks>
/// Implementation may use <see cref="IGraphEqualityComparer.Context"/>
/// when visiting non-graph-supported sub-comparer.
/// </remarks>
int GetHashCode([DisallowNull] T obj, IGraphComparerContext context);
}
IGraphComparerContext
IGraphComparerContext is interface for contextes that track hashed objects.
/// <summary>Graph comparer context</summary>
public interface IGraphComparerContext
{
/// <summary>Add visitation of <paramref name="x"/>.</summary>
/// <returns>true if was added, false if has already been added.</returns>
bool Add<T>(in T x);
/// <summary>Check previous visitation of <paramref name="x"/>.</summary>
/// <returns>true if previous visitation exists</returns>
bool Contains<T>(in T x);
}
GraphComparerContext is the default implementation for IGraphComparerContext.
IGraphComparerContext graphComparerContext = new GraphComparerContext();
IGraphComparerContext2 is interface for contextes that track pairs that have already been compared.
/// <summary>Graph comparer context</summary>
public interface IGraphComparerContext2
{
/// <summary>Add visitation of <paramref name="x"/> to <paramref name="y"/>.</summary>
/// <returns>true if was added, false if has already been added.</returns>
bool Add<T>(in T x, in T y);
/// <summary>
/// Check if equality of <paramref name="x"/> to <paramref name="y"/> has been compared.
/// </summary>
/// <returns>true if previous visitation exists</returns>
bool Contains<T>(in T x, in T y);
}
GraphComparerContext2 is the default implementation for IGraphComparerContext2.
IGraphComparerContext2 graphComparerContext = new GraphComparerContext2();
ICyclical
ICyclical is interface for indicating whether instance may contain cycle back to itself.
/// <summary>
/// Indicates that the inner contents of the instance may contain a cycle back to the instance.
///
/// This is used with graph comparer, equality-comparer and cloner implementations.
/// </summary>
public interface ICyclical
{
/// <summary>Can instance be cyclic. False = never, True = yes/possibly.</summary>
[IgnoreDataMember] bool IsCyclical { get; set; }
}