IGraphCloneable
IGraphCloneable is interface for cyclic graph cloneable objects.
/// <summary>Object cloneable with graph awareness.</summary>
public interface IGraphCloneable
{
// <summary>Can value be cyclic</summary>
//bool IsCyclical { get; }
/// <summary>Clone </summary>
/// <param name="context">Object graph reference mapping.</param>
object Clone(IGraphClonerContext context);
}
IGraphClonerContext
IGraphClonerContext is interface for contextes that track hashed objects.
/// <summary>Graph cloner context</summary>
public interface IGraphClonerContext
{
/// <summary>Associate <paramref name="clone"/> as clone of <paramref name="src"/>.</summary>
/// <returns>true if was added, false if has already been added.</returns>
bool Add<T>(in T src, in T clone);
/// <summary>Test whether <paramref name="src"/> has been marked cloned.</summary>
/// <returns>true if <paramref name="src"/> is cloned</returns>
bool Contains<T>(in T src);
/// <summary>Get cloned counterpart of <paramref name="src"/>.</summary>
/// <returns>true if <paramref name="clone"/> existed.</returns>
bool TryGet<T>(in T src, out T clone);
}
GraphClonerContext is the default implementation for IGraphClonerContext.
IGraphClonerContext graphClonerContext = new GraphClonerContext();
Implementation
Example implementation of ICloneable for non-cyclic class.
public class MyRecord : ICloneable
{
/// <summary></summary>
public readonly int Id;
/// <summary></summary>
public readonly string Name;
/// <summary></summary>
public MyRecord(int id, string name)
{
Id = id;
Name = name;
}
/// <summary>Clone</summary>
public object Clone() => new MyRecord(Id, Name);
}
Cloner<T>.Implementation adapts ICloneable to both ICloner<T> and IGraphCloner<T>.
// Create cloner
ICloner<MyRecord> recordCloner = Cloner<MyRecord>.Instance;
.Clone(obj) forwards call to ICloneable.
// Create record
MyRecord myRecord = new MyRecord(1, "Abc-123");
// Clone record
MyRecord clone1 = recordCloner.Clone(myRecord);
// Compare object references
WriteLine(myRecord == clone1); // false
Cyclic Implementation
Example implementation of IGraphCloneable and ICloner in a cyclic Node class.
/// <summary>Graph node</summary>
public class Node : IGraphCloneable, ICloneable
{
/// <summary>Id</summary>
public readonly int Id;
/// <summary>Forward edges/summary>
public readonly List<Node> Edges = new List<Node>();
/// <summary>Create node</summary>
public Node(int id) => Id = id;
/// <summary>Clone</summary>
public object Clone(IGraphClonerContext context)
{
// Get existing clone
if (context.TryGet(this, out Node clone0)) return clone0;
// Create clone
Node clone = new Node(Id);
// Add to context
context.Add(this, clone);
// Hash in edges
foreach (Node n in Edges) clone.Edges.Add((Node)n.Clone(context));
// Return
return clone;
}
/// <summary>Clone</summary>
public object Clone()
{
// Get previous context
IGraphClonerContext? prevContext = IGraphCloner.Context.Value;
// Place here context
IGraphClonerContext context = prevContext ?? setContext(new GraphClonerContext())!;
try
{
return Clone(context);
}
finally
{
// Revert to previous context
IGraphCloner.Context.Value = prevContext;
}
}
/// <summary>Assign <paramref name="context"/> to <see cref="IGraphCloner.Context"/> and return it.</summary>
/// <returns><paramref name="context"/></returns>
static IGraphClonerContext? setContext(IGraphClonerContext context) { IGraphCloner.Context.Value = context; return context; }
}
Create graph.
// Create graph
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
node1.Edges.Add(node2);
node2.Edges.Add(node3);
node3.Edges.Add(node1);
Node[] graph = new Node[] { node1, node2, node3 };
Cloner<T> cloner forwards calls to IGraphCloneable.
// Create cloner
IGraphCloner<Node> nodeCloner = Cloner<Node>.Instance;
// Clone graph
Node clone1 = nodeCloner.Clone(node1, new GraphClonerContext());
// Compare object references
WriteLine(clone1 == node1); // false
.Clone(IGraphClonerContext) can be called directly.
// Clone graph
Node clone1 = (Node)node1.Clone(new GraphClonerContext());
// Compare object references
WriteLine(clone1 == node1); // false
Cloner<T> cloner forwards calls to ICloneable.
// Create cloner
ICloner<Node> nodeCloner = Cloner<Node>.Instance;
// Clone node
Node clonedNode1 = nodeCloner.Clone(node1);
// Compare object references
WriteLine(clonedNode1 == node1); // false
.Clone() can be called as is.
// Clone node
Node clonedNode1 = (Node)node1.Clone();
// Compare object references
WriteLine(clonedNode1 == node1); // false
Graph nodes are cloned.
⇒
Full Example
Full example
using System;
using System.Collections.Generic;
using Avalanche.Utilities;
using static System.Console;
class graphclonable
{
public static void Run()
{
{
// <00>
IGraphClonerContext graphClonerContext = new GraphClonerContext();
// </00>
}
{
// <01>
// Create cloner
ICloner<MyRecord> recordCloner = Cloner<MyRecord>.Instance;
// </01>
// <02>
// Create record
MyRecord myRecord = new MyRecord(1, "Abc-123");
// Clone record
MyRecord clone1 = recordCloner.Clone(myRecord);
// Compare object references
WriteLine(myRecord == clone1); // false
// </02>
}
{
// <11>
// Create graph
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
node1.Edges.Add(node2);
node2.Edges.Add(node3);
node3.Edges.Add(node1);
Node[] graph = new Node[] { node1, node2, node3 };
// </11>
{
// <12>
// Create cloner
IGraphCloner<Node> nodeCloner = Cloner<Node>.Instance;
// Clone graph
Node clone1 = nodeCloner.Clone(node1, new GraphClonerContext());
// Compare object references
WriteLine(clone1 == node1); // false
// </12>
}
{
// <13>
// Clone graph
Node clone1 = (Node)node1.Clone(new GraphClonerContext());
// Compare object references
WriteLine(clone1 == node1); // false
// </13>
}
{
// <14>
// Create cloner
ICloner<Node> nodeCloner = Cloner<Node>.Instance;
// Clone node
Node clonedNode1 = nodeCloner.Clone(node1);
// Compare object references
WriteLine(clonedNode1 == node1); // false
// </14>
}
{
// <15>
// Clone node
Node clonedNode1 = (Node)node1.Clone();
// Compare object references
WriteLine(clonedNode1 == node1); // false
// </15>
}
}
}
// <98>
public class MyRecord : ICloneable
{
/// <summary></summary>
public readonly int Id;
/// <summary></summary>
public readonly string Name;
/// <summary></summary>
public MyRecord(int id, string name)
{
Id = id;
Name = name;
}
/// <summary>Clone</summary>
public object Clone() => new MyRecord(Id, Name);
}
// </98>
// <99>
/// <summary>Graph node</summary>
public class Node : IGraphCloneable, ICloneable
{
/// <summary>Id</summary>
public readonly int Id;
/// <summary>Forward edges/summary>
public readonly List<Node> Edges = new List<Node>();
/// <summary>Create node</summary>
public Node(int id) => Id = id;
/// <summary>Clone</summary>
public object Clone(IGraphClonerContext context)
{
// Get existing clone
if (context.TryGet(this, out Node clone0)) return clone0;
// Create clone
Node clone = new Node(Id);
// Add to context
context.Add(this, clone);
// Hash in edges
foreach (Node n in Edges) clone.Edges.Add((Node)n.Clone(context));
// Return
return clone;
}
/// <summary>Clone</summary>
public object Clone()
{
// Get previous context
IGraphClonerContext? prevContext = IGraphCloner.Context.Value;
// Place here context
IGraphClonerContext context = prevContext ?? setContext(new GraphClonerContext())!;
try
{
return Clone(context);
}
finally
{
// Revert to previous context
IGraphCloner.Context.Value = prevContext;
}
}
/// <summary>Assign <paramref name="context"/> to <see cref="IGraphCloner.Context"/> and return it.</summary>
/// <returns><paramref name="context"/></returns>
static IGraphClonerContext? setContext(IGraphClonerContext context) { IGraphCloner.Context.Value = context; return context; }
}
// </99>
}