public void TestPushIndexAndMember() { var obj = new Class { ListMember = { new Class(), new Class(), new Class() } }; var nodeContainer = new NodeContainer(); var rootNode = nodeContainer.GetOrCreateNode(obj); var path = new GraphNodePath(rootNode); path.PushMember(nameof(Class.ListMember)); path.PushTarget(); path.PushIndex(new NodeIndex(1)); path.PushMember(nameof(Class.IntMember)); var targetNode = nodeContainer.GetNode(obj.ListMember[1]); var intNode = targetNode[nameof(Class.IntMember)]; var nodes = new IGraphNode[] { rootNode, rootNode[nameof(Class.ListMember)], rootNode[nameof(Class.ListMember)].Target, targetNode, intNode }; Assert.NotNull(targetNode); Assert.NotNull(intNode); Assert.False(path.IsEmpty); AssertAreEqual(rootNode, path.RootNode); AssertAreEqual(intNode, path.GetNode()); var i = 0; foreach (var node in path) { AssertAreEqual(nodes[i++], node); } AssertAreEqual(nodes.Length, i); }
public void TestCloneNewRoot() { var obj1 = new Class { ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; var obj2 = new Class { ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; var nodeContainer = new NodeContainer(); var newRoot = nodeContainer.GetOrCreateNode(obj2); var path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj1)); var clone = path1.Clone(newRoot); Assert.AreNotEqual(newRoot, path1.RootNode); Assert.AreEqual(newRoot, clone.RootNode); Assert.AreEqual(path1.IsValid, clone.IsValid); Assert.AreEqual(path1.IsEmpty, clone.IsEmpty); var path2 = path1.PushMember(nameof(Class.ClassMember)).PushTarget().PushMember(nameof(Class.IntMember)); clone = path2.Clone(newRoot); Assert.AreNotEqual(newRoot, path2.RootNode); Assert.AreEqual(newRoot, clone.RootNode); Assert.AreEqual(path2.IsValid, clone.IsValid); Assert.AreEqual(path2.IsEmpty, clone.IsEmpty); var path3 = path1.PushMember(nameof(Class.ListMember)).PushIndex(new Index(1)).PushMember(nameof(Class.IntMember)); clone = path3.Clone(newRoot); Assert.AreNotEqual(newRoot, path3.RootNode); Assert.AreEqual(newRoot, clone.RootNode); Assert.AreEqual(path3.IsValid, clone.IsValid); Assert.AreEqual(path3.IsEmpty, clone.IsEmpty); }
public void TestClone() { var obj = new Class { ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; var nodeContainer = new NodeContainer(); var path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); var clone = path1.Clone(); AssertAreEqual(path1, clone); AssertAreEqual(path1.GetHashCode(), clone.GetHashCode()); AssertAreEqual(path1.RootNode, clone.RootNode); AssertAreEqual(path1.IsValid, clone.IsValid); AssertAreEqual(path1.IsEmpty, clone.IsEmpty); AssertAreEqual(path1.GetNode(), clone.GetNode()); var path2 = path1.PushMember(nameof(Class.ClassMember)).PushTarget().PushMember(nameof(Class.IntMember)); clone = path2.Clone(); AssertAreEqual(path2, clone); AssertAreEqual(path2.RootNode, clone.RootNode); AssertAreEqual(path2.IsValid, clone.IsValid); AssertAreEqual(path2.IsEmpty, clone.IsEmpty); AssertAreEqual(path2.GetNode(), clone.GetNode()); var path3 = path1.PushMember(nameof(Class.ListMember)).PushIndex(new Index(1)).PushMember(nameof(Class.IntMember)); clone = path3.Clone(); AssertAreEqual(path3, clone); AssertAreEqual(path3.RootNode, clone.RootNode); AssertAreEqual(path3.IsValid, clone.IsValid); AssertAreEqual(path3.IsEmpty, clone.IsEmpty); AssertAreEqual(path3.GetNode(), clone.GetNode()); }
public void TestPushStructMember() { var obj = new Class { StructMember = { StringMember = "aa" } }; var nodeContainer = new NodeContainer(); var rootNode = nodeContainer.GetOrCreateNode(obj); var path = new GraphNodePath(rootNode); path.PushMember(nameof(Class.StructMember)); path.PushTarget(); path.PushMember(nameof(Struct.StringMember)); var structNode = rootNode[nameof(Class.StructMember)]; var targetNode = rootNode[nameof(Class.StructMember)].Target; var memberNode = rootNode[nameof(Class.StructMember)].Target[nameof(Struct.StringMember)]; var nodes = new IGraphNode[] { rootNode, structNode, targetNode, memberNode }; Assert.NotNull(targetNode); Assert.NotNull(memberNode); Assert.False(path.IsEmpty); AssertAreEqual(rootNode, path.RootNode); AssertAreEqual(memberNode, path.GetNode()); var i = 0; foreach (var node in path) { AssertAreEqual(nodes[i++], node); } AssertAreEqual(nodes.Length, i); }
public void TestGetParent() { var obj = new Class { StructMember = { StringMember = "aa" }, ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; var nodeContainer = new NodeContainer(); var rootNode = nodeContainer.GetOrCreateNode(obj); var path = new GraphNodePath(rootNode); path.PushMember(nameof(Class.IntMember)); var parentPath = new GraphNodePath(rootNode); AssertAreEqual(parentPath, path.GetParent()); path = new GraphNodePath(rootNode); path.PushMember(nameof(Class.StructMember)); path.PushMember(nameof(Struct.StringMember)); parentPath = new GraphNodePath(rootNode); parentPath.PushMember(nameof(Class.StructMember)); AssertAreEqual(parentPath, path.GetParent()); path = new GraphNodePath(rootNode); path.PushMember(nameof(Class.ClassMember)); path.PushTarget(); parentPath = new GraphNodePath(rootNode); parentPath.PushMember(nameof(Class.ClassMember)); AssertAreEqual(parentPath, path.GetParent()); path = new GraphNodePath(rootNode); path.PushMember(nameof(Class.ClassMember)); path.PushTarget(); path.PushMember(nameof(Class.IntMember)); parentPath = new GraphNodePath(rootNode); parentPath.PushMember(nameof(Class.ClassMember)); parentPath.PushTarget(); AssertAreEqual(parentPath, path.GetParent()); path = new GraphNodePath(rootNode); path.PushMember(nameof(Class.ListMember)); path.PushIndex(new NodeIndex(1)); parentPath = new GraphNodePath(rootNode); parentPath.PushMember(nameof(Class.ListMember)); AssertAreEqual(parentPath, path.GetParent()); path = new GraphNodePath(rootNode); path.PushMember(nameof(Class.ListMember)); path.PushIndex(new NodeIndex(1)); path.PushMember(nameof(Class.IntMember)); parentPath = new GraphNodePath(rootNode); parentPath.PushMember(nameof(Class.ListMember)); parentPath.PushIndex(new NodeIndex(1)); AssertAreEqual(parentPath, path.GetParent()); }
/// <inheritdoc/> public override GraphNodePath GetNodePath() { var path = new GraphNodePath(Editor.NodeContainer.GetNode(Asset.Asset)); path.PushMember(nameof(EntityHierarchy.Hierarchy)); path.PushTarget(); path.PushMember(nameof(EntityHierarchy.Hierarchy.Parts)); path.PushTarget(); path.PushIndex(new NodeIndex(Id.ObjectId)); path.PushMember(nameof(EntityDesign.Entity)); path.PushTarget(); return(path); }
/// <inheritdoc/> GraphNodePath IAssetPropertyProviderViewModel.GetAbsolutePathToRootNode() { var asset = Method.Editor.Asset.Asset; var path = new GraphNodePath(Method.Editor.Session.AssetNodeContainer.GetNode(asset)); path.PushMember(nameof(VisualScriptAsset.Methods)); path.PushTarget(); path.PushIndex(new NodeIndex(asset.Methods.IndexOf(Method.Method.Method))); path.PushMember(nameof(Scripts.Method.Links)); path.PushTarget(); path.PushIndex(new NodeIndex(link.Id)); path.PushTarget(); return(path); }
public void TestPushTarget() { var obj = new Class { ClassMember = new Class() }; var nodeContainer = new NodeContainer(); var rootNode = nodeContainer.GetOrCreateNode(obj); var path = new GraphNodePath(rootNode); path.PushMember(nameof(Class.ClassMember)); path.PushTarget(); var targetNode = nodeContainer.GetNode(obj.ClassMember); var nodes = new IGraphNode[] { rootNode, rootNode[nameof(Class.ClassMember)], targetNode }; Assert.NotNull(targetNode); Assert.False(path.IsEmpty); AssertAreEqual(rootNode, path.RootNode); AssertAreEqual(targetNode, path.GetNode()); var i = 0; foreach (var node in path) { AssertAreEqual(nodes[i++], node); } AssertAreEqual(nodes.Length, i); }
/// <inheritdoc/> public override GraphNodePath GetNodePath() { var path = new GraphNodePath(Editor.NodeContainer.GetNode(Asset.Asset)); path.PushMember(nameof(UIAsset.Hierarchy)); path.PushTarget(); return(path); }
/// <inheritdoc /> protected override GraphNodePath GetNodePath() { var path = new GraphNodePath(Editor.Session.AssetNodeContainer.GetNode(Editor.Asset.Asset)); path.PushMember(nameof(GraphicsCompositorAsset.Cameras)); path.PushTarget(); path.PushIndex(new Index(Editor.Asset.Asset.Cameras.IndexOf(CameraSlot))); return(path); }
/// <inheritdoc /> protected override GraphNodePath GetNodePath() { var path = new GraphNodePath(Editor.Session.AssetNodeContainer.GetNode(Editor.Asset.Asset)); path.PushMember(nameof(GraphicsCompositorAsset.SharedRenderers)); path.PushTarget(); path.PushIndex(new NodeIndex(Editor.Asset.Asset.SharedRenderers.IndexOf(sharedRenderer))); return(path); }
GraphNodePath IAssetPropertyProviderViewModel.GetAbsolutePathToRootNode() { var path = new GraphNodePath(Editor.Session.AssetNodeContainer.GetNode(Editor.Asset.Asset)); path.PushMember(nameof(SpriteSheetAsset.Sprites)); path.PushIndex(new NodeIndex(Index)); path.PushTarget(); return(path); }
public void TestSimpleObjectInitialPath() { var nodeContainer = new NodeContainer(); var instance = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; var rootNode = nodeContainer.GetOrCreateNode(instance); var container = new SimpleClass { Member2 = instance }; var containerNode = nodeContainer.GetOrCreateNode(container); var initialPath = new GraphNodePath(containerNode); initialPath.PushMember(nameof(SimpleClass.Member2)); initialPath.PushTarget(); var visitor = new TestVisitor(); visitor.Visit(rootNode, null, initialPath); var expectedNodes = new IGraphNode[] { rootNode, rootNode[nameof(SimpleClass.Member1)], rootNode[nameof(SimpleClass.Member2)], rootNode[nameof(SimpleClass.Member2)].Target, rootNode[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], rootNode[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], }; var expectedPaths = new GraphNodePath[6]; expectedPaths[0] = initialPath.Clone(); expectedPaths[1] = initialPath.Clone(); expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); expectedPaths[2] = initialPath.Clone(); expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); expectedPaths[3] = expectedPaths[2].Clone(); expectedPaths[3].PushTarget(); expectedPaths[4] = expectedPaths[3].Clone(); expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); expectedPaths[5] = expectedPaths[3].Clone(); expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); }
private void GenerateChildren(IGraphNode targetNode, GraphNodePath targetNodePath) { // Node representing a member with a reference to another object if (SourceNode != targetNode && SourceNode.Content.IsReference) { var objectReference = SourceNode.Content.Reference as ObjectReference; // Discard the children of the referenced object if requested by the property provider if (objectReference != null && !Owner.PropertiesProvider.ShouldExpandReference(SourceNode.Content as MemberContent, objectReference)) { return; } var refEnum = SourceNode.Content.Reference as ReferenceEnumerable; if (refEnum != null) { foreach (var reference in refEnum) { // Discard the children of the referenced object if requested by the property provider if (reference != null && !Owner.PropertiesProvider.ShouldExpandReference(SourceNode.Content as MemberContent, reference)) { return; } } } } var dictionary = targetNode.Content.Descriptor as DictionaryDescriptor; var list = targetNode.Content.Descriptor as CollectionDescriptor; // Node containing a collection of references to other objects if (SourceNode == targetNode && targetNode.Content.IsReference) { var referenceEnumerable = targetNode.Content.Reference as ReferenceEnumerable; if (referenceEnumerable != null) { // We create one node per item of the collection, unless requested by the property provide to not expand the reference. foreach (var reference in referenceEnumerable) { // The type might be a boxed primitive type, such as float, if the collection has object as generic argument. // In this case, we must set the actual type to have type converter working, since they usually can't convert // a boxed float to double for example. Otherwise, we don't want to have a node type that is value-dependent. var type = reference.TargetNode != null && reference.TargetNode.Content.IsPrimitive ? reference.TargetNode.Content.Type : reference.Type; var observableNode = Owner.ObservableViewModelService.ObservableNodeFactory(Owner, null, false, targetNode, targetNodePath, type, reference.Index); AddChild(observableNode); observableNode.Initialize(); } } } // Node containing a dictionary of primitive values else if (dictionary != null && targetNode.Content.Value != null) { // TODO: there is no way to discard items of such collections, without discarding the collection itself. Could this be needed at some point? // We create one node per item of the collection. foreach (var key in dictionary.GetKeys(targetNode.Content.Value)) { var index = new Index(key); var observableChild = Owner.ObservableViewModelService.ObservableNodeFactory(Owner, null, true, targetNode, targetNodePath, dictionary.ValueType, index); AddChild(observableChild); observableChild.Initialize(); } } // Node containing a list of primitive values else if (list != null && targetNode.Content.Value != null) { // TODO: there is no way to discard items of such collections, without discarding the collection itself. Could this be needed at some point? // We create one node per item of the collection. for (int i = 0; i < list.GetCollectionCount(targetNode.Content.Value); ++i) { var index = new Index(i); var observableChild = Owner.ObservableViewModelService.ObservableNodeFactory(Owner, null, true, targetNode, targetNodePath, list.ElementType, index); AddChild(observableChild); observableChild.Initialize(); } } // Node containing a single non-reference primitive object else { foreach (var child in targetNode.Children) { var memberContent = (MemberContent)child.Content; var descriptor = (MemberDescriptorBase)memberContent.Member; var displayAttribute = TypeDescriptorFactory.Default.AttributeRegistry.GetAttribute <DisplayAttribute>(descriptor.MemberInfo); if (displayAttribute == null || displayAttribute.Browsable) { // The path is the source path here - the target path might contain the target resolution that we don't want at that point if (Owner.PropertiesProvider.ShouldConstructMember(memberContent)) { var childPath = targetNodePath.PushMember(child.Name); var observableChild = Owner.ObservableViewModelService.ObservableNodeFactory(Owner, child.Name, child.Content.IsPrimitive, child, childPath, child.Content.Type, Index.Empty); AddChild(observableChild); observableChild.Initialize(); } } } } }
private void GenerateChildren(IContentNode targetNode, GraphNodePath targetNodePath, Index index) { // Set the default policy for expanding reference children. ExpandReferencePolicy = ExpandReferencePolicy.Full; // Node representing a member with a reference to another object if (SourceNode != targetNode && SourceNode.IsReference) { var objectReference = SourceNode.TargetReference ?? SourceNode.ItemReferences?[index]; // Discard the children of the referenced object if requested by the property provider if (objectReference != null) { ExpandReferencePolicy = Owner.PropertiesProvider.ShouldExpandReference(SourceNode as MemberContent, objectReference); if (ExpandReferencePolicy == ExpandReferencePolicy.None) { return; } } } var dictionary = targetNode.Descriptor as DictionaryDescriptor; var list = targetNode.Descriptor as CollectionDescriptor; var initializedChildren = new List <NodeViewModel>(); // Node containing a collection of references to other objects if (SourceNode == targetNode && targetNode.IsReference) { var referenceEnumerable = targetNode.ItemReferences; if (referenceEnumerable != null) { // We create one node per item of the collection, we will check later if the reference should be expanded. foreach (var reference in referenceEnumerable) { // The type might be a boxed primitive type, such as float, if the collection has object as generic argument. // In this case, we must set the actual type to have type converter working, since they usually can't convert // a boxed float to double for example. Otherwise, we don't want to have a node type that is value-dependent. var type = reference.TargetNode != null && reference.TargetNode.IsPrimitive ? reference.TargetNode.Type : referenceEnumerable.ElementType; var child = Owner.GraphViewModelService.GraphNodeViewModelFactory(Owner, null, false, targetNode, targetNodePath, type, reference.Index); AddChild(child); child.Initialize(); initializedChildren.Add(child); } } } // Node containing a dictionary of primitive values else if (dictionary != null && targetNode.Value != null) { // TODO: there is no way to discard items of such collections, without discarding the collection itself. Could this be needed at some point? // We create one node per item of the collection. foreach (var key in dictionary.GetKeys(targetNode.Value)) { var newIndex = new Index(key); var child = Owner.GraphViewModelService.GraphNodeViewModelFactory(Owner, null, true, targetNode, targetNodePath, dictionary.ValueType, newIndex); AddChild(child); child.Initialize(); initializedChildren.Add(child); } } // Node containing a list of primitive values else if (list != null && targetNode.Value != null) { // TODO: there is no way to discard items of such collections, without discarding the collection itself. Could this be needed at some point? // We create one node per item of the collection. for (int i = 0; i < list.GetCollectionCount(targetNode.Value); ++i) { var newIndex = new Index(i); var child = Owner.GraphViewModelService.GraphNodeViewModelFactory(Owner, null, true, targetNode, targetNodePath, list.ElementType, newIndex); AddChild(child); child.Initialize(); initializedChildren.Add(child); } } // Node containing a single non-reference primitive object else { var objectContent = (IObjectNode)targetNode; foreach (var memberContent in objectContent.Members) { var descriptor = (MemberDescriptorBase)memberContent.MemberDescriptor; var displayAttribute = TypeDescriptorFactory.Default.AttributeRegistry.GetAttribute <DisplayAttribute>(descriptor.MemberInfo); if (displayAttribute == null || displayAttribute.Browsable) { // The path is the source path here - the target path might contain the target resolution that we don't want at that point if (Owner.PropertiesProvider.ShouldConstructMember(memberContent, ExpandReferencePolicy)) { var childPath = targetNodePath.PushMember(memberContent.Name); var child = Owner.GraphViewModelService.GraphNodeViewModelFactory(Owner, memberContent.Name, memberContent.IsPrimitive, memberContent, childPath, memberContent.Type, Index.Empty); AddChild(child); child.Initialize(); initializedChildren.Add(child); } } } } // Call FinalizeInitialization on all created nodes after they were all initialized. foreach (var child in initializedChildren) { child.FinalizeInitialization(); } }
private void GenerateChildren(IGraphNode modelNode, GraphNodePath graphNodePath) { if (modelNode.Content.IsReference && modelNode.Content.ShouldProcessReference) { var referenceEnumerable = modelNode.Content.Reference as ReferenceEnumerable; if (referenceEnumerable != null) { // If the reference should not be processed, we still need to create an observable node for each entry of the enumerable. // These observable nodes will have the same source node that their parent so we use this information to prevent // the infinite recursion that could occur due to the fact that these child nodes will have the same model nodes (like primitive types) // while holding an enumerable reference. //if (modelNode.Content.ShouldProcessReference || ModelNodeParent.sourceNode != modelNode) { // Note: we are making a copy of the reference list because it can be updated from the Initialize method of the // observable node in the case of scene objects. Doing this is a hack, but parts of this framework will be redesigned later to improve this foreach (var reference in referenceEnumerable.ToList()) { // The type might be a boxed primitive type, such as float, if the collection has object as generic argument. // In this case, we must set the actual type to have type converter working, since they usually can't convert // a boxed float to double for example. Otherwise, we don't want to have a node type that is value-dependent. var type = reference.TargetNode != null && reference.TargetNode.Content.IsPrimitive ? reference.TargetNode.Content.Type : reference.Type; bool shouldConstruct = Owner.PropertiesProvider.ShouldConstructNode(modelNode, reference.Index); if (shouldConstruct) { var observableNode = Owner.ObservableViewModelService.ObservableNodeFactory(Owner, null, false, modelNode, graphNodePath, type, reference.Index); AddChild(observableNode); observableNode.Initialize(); } } } } } else { var dictionary = modelNode.Content.Descriptor as DictionaryDescriptor; var list = modelNode.Content.Descriptor as CollectionDescriptor; if (dictionary != null && modelNode.Content.Value != null) { // Dictionary of primitive objects foreach (var key in dictionary.GetKeys(modelNode.Content.Value)) { var index = new Index(key); bool shouldConstruct = Owner.PropertiesProvider.ShouldConstructNode(modelNode, index); if (shouldConstruct) { var observableChild = Owner.ObservableViewModelService.ObservableNodeFactory(Owner, null, true, modelNode, graphNodePath, dictionary.ValueType, index); AddChild(observableChild); observableChild.Initialize(); } } } else if (list != null && modelNode.Content.Value != null) { // List of primitive objects for (int i = 0; i < list.GetCollectionCount(modelNode.Content.Value); ++i) { var index = new Index(i); bool shouldConstruct = Owner.PropertiesProvider.ShouldConstructNode(modelNode, index); if (shouldConstruct) { var observableChild = Owner.ObservableViewModelService.ObservableNodeFactory(Owner, null, true, modelNode, graphNodePath, list.ElementType, index); AddChild(observableChild); observableChild.Initialize(); } } } else { // Single non-reference primitive object foreach (var child in modelNode.Children) { bool shouldConstruct = Owner.PropertiesProvider.ShouldConstructNode(child, Index.Empty); if (shouldConstruct) { var childPath = graphNodePath.PushMember(child.Name); var observableChild = Owner.ObservableViewModelService.ObservableNodeFactory(Owner, child.Name, child.Content.IsPrimitive, child, childPath, child.Content.Type, Index.Empty); AddChild(observableChild); observableChild.Initialize(); } } } } }
public void TestChangeStruct() { var nodeContainer = new NodeContainer(); var obj = new[] { new Struct { Member1 = "aa" }, new Struct { Member1 = "bb" }, new Struct { Member1 = "cc" } }; var instance = new ComplexClass { Member4 = obj[0] }; var rootNode = nodeContainer.GetOrCreateNode(instance); var listener = new GraphNodeChangeListener(rootNode); var node = rootNode.GetChild(nameof(ComplexClass.Member4)); var path = new GraphNodePath(rootNode).PushMember(nameof(ComplexClass.Member4)); Assert.AreEqual("aa", node.GetChild(nameof(Struct.Member1)).Content.Retrieve()); TestContentChange(listener, node, ContentChangeType.ValueChange, Index.Empty, obj[0], obj[1], path, () => node.Content.Update(obj[1])); Assert.AreEqual("bb", node.GetChild(nameof(Struct.Member1)).Content.Retrieve()); TestContentChange(listener, node, ContentChangeType.ValueChange, Index.Empty, obj[1], obj[2], path, () => node.Content.Update(obj[2])); Assert.AreEqual("cc", node.GetChild(nameof(Struct.Member1)).Content.Retrieve()); TestContentChange(listener, node.GetChild(nameof(Struct.Member1)), ContentChangeType.ValueChange, Index.Empty, "cc", "dd", path.PushMember(nameof(Struct.Member1)), () => node.GetChild(nameof(Struct.Member1)).Content.Update("dd")); Assert.AreEqual("dd", node.GetChild(nameof(Struct.Member1)).Content.Retrieve()); }
public void TestEquals() { // Note: comparing GraphNodePath.GetHashCode() returns true when the root node is equivalent. This is because the root node is the only invariant. var obj = new Class { StructMember = { StringMember = "aa" }, ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; var nodeContainer = new NodeContainer(); var path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path1.PushMember(nameof(Class.IntMember)); var path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path2.PushMember(nameof(Class.IntMember)); AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); AssertAreEqual(path1, path2); path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path1.PushMember(nameof(Class.ClassMember)); AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); AssertAreNotEqual(path1, path2); path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path2.PushMember(nameof(Class.ClassMember)); AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); AssertAreEqual(path1, path2); path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path1.PushMember(nameof(Class.ClassMember)); path1.PushTarget(); AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); AssertAreNotEqual(path1, path2); path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path2.PushMember(nameof(Class.ClassMember)); path2.PushTarget(); AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); AssertAreEqual(path1, path2); path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path1.PushMember(nameof(Class.ClassMember)); path1.PushTarget(); path1.PushMember(nameof(Class.IntMember)); AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); AssertAreNotEqual(path1, path2); path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path2.PushMember(nameof(Class.ClassMember)); path2.PushTarget(); path2.PushMember(nameof(Class.IntMember)); AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); AssertAreEqual(path1, path2); path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path1.PushMember(nameof(Class.ListMember)); path1.PushIndex(new NodeIndex(0)); AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); AssertAreNotEqual(path1, path2); path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path2.PushMember(nameof(Class.ListMember)); path2.PushIndex(new NodeIndex(0)); AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); AssertAreEqual(path1, path2); path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); path2.PushMember(nameof(Class.ListMember)); path2.PushIndex(new NodeIndex(1)); AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); AssertAreNotEqual(path1, path2); }