/// <summary> /// Set the value of the specified node of the hierarchy. /// if the node doesn't exist, it is created. /// </summary> /// <param name="path"></param> /// <returns></returns> public TValue this[HierarchyPath <TKey> path] { get { if (this.TryGetValue(path, out var value)) { return(value); } throw new KeyNotFoundException($"path '{path}' doesn't exist or has no value"); } set { bool isLocked = false; try { this.writeLock.Enter(ref isLocked); var writer = new SetOrAddNodeValueWriter <TKey, TValue, ImmutableNode <TKey, TValue> >(createNode: key => new ImmutableNode <TKey, TValue>(key)); // if the root node has changed, it substitutes the existing root node. this.rootNode = writer.SetValue(this.rootNode, path, value); } finally { if (isLocked) { this.writeLock.Exit(); } } } }
public void ImmutableNode_removes_child_from_clone_of_current_instance() { // ARRANGE var child = new ImmutableNode <string, int>("a"); var node = new ImmutableNode <string, int>(null, 1).AddChild(child); // ACT var result = node.RemoveChild(child); // ASSERT Assert.NotSame(node, result); Assert.True(node.HasChildNodes); Assert.False(result.HasChildNodes); Assert.Equal(1, result.Value); var(found, readChild) = node.TryGetChildNode("a"); Assert.True(found); Assert.Same(child, readChild); (found, readChild) = result.TryGetChildNode("a"); Assert.False(found); }
public void ImmutableNode_adds_child_to_clone_of_current_instance() { // ARRANGE var child = new ImmutableNode <string, int>("a"); var node = new ImmutableNode <string, int>(); node.SetValue(1); // ACT var result = node.AddChild(child); // ASSERT // node creates a clone with the new child. Assert.NotSame(node, result); Assert.False(node.HasChildNodes); Assert.True(result.HasChildNodes); Assert.True(result.HasValue); Assert.Equal(1, result.Value); Assert.Same(child, result.ChildNodes.Single()); var(found, addedChild) = node.TryGetChildNode("a"); Assert.False(found); (found, addedChild) = result.TryGetChildNode("a"); Assert.True(found); Assert.Same(child, addedChild); }
public void ImmutableNode_replaces_child_in_clone_current_instance() { // ARRANGE var child = new ImmutableNode <string, int>("a"); var node = new ImmutableNode <string, int>().AddChild(child); node.SetValue(1); var secondChild = new ImmutableNode <string, int>("a"); // ACT var result = node.ReplaceChild(child, secondChild); // ASSERT Assert.NotSame(node, result); Assert.True(node.HasChildNodes); Assert.True(result.HasChildNodes); Assert.Equal(1, result.Value); var(found, readChildNode) = node.TryGetChildNode("a"); Assert.True(found); Assert.Same(child, readChildNode); (found, readChildNode) = result.TryGetChildNode("a"); Assert.True(found); Assert.Same(secondChild, readChildNode); }
private bool TryUseTupleSyntax(IReadOnlyList <MetadataTypeReference> args, out ImmutableNode <string> tupleSyntax) { var tupleElements = new List <MetadataTypeReference>(args.Count); while (args.Count == 8) { if (!(args[7] is GenericInstantiationTypeReference genericRest && IsValueTuple(genericRest, minArgs: 1))) { tupleSyntax = null; return(false); } for (var i = 0; i < 7; i++) { tupleElements.Add(args[i]); } args = genericRest.GenericTypeArguments; } for (var i = 0; i < args.Count; i++) { tupleElements.Add(args[i]); } var current = new ImmutableNode <string>(tupleElements[tupleElements.Count - 1].Accept(this), ")", null); for (var i = tupleElements.Count - 2; i >= 0; i--) { current = new ImmutableNode <string>(tupleElements[i].Accept(this), ", ", current); } tupleSyntax = new ImmutableNode <string>(null, "(", current); return(true); }
private static ImmutableNode <string> BuildNameWithArity(string rawName) { var(name, arity) = NameUtils.ParseGenericArity(rawName); var arityBuilder = (ImmutableNode <string>)null; if (arity != 0) { arityBuilder = GenericParameterListEnd; for (var i = 1; i < arity; i++) { arityBuilder = new ImmutableNode <string>(null, ",", arityBuilder); } arityBuilder = new ImmutableNode <string>(null, "<", arityBuilder); } return(new ImmutableNode <string>(null, name, arityBuilder)); }
/// <summary> /// Removes the specifed node fro the hierarchy. If the node has child nodes and /// <paramref name="recurse"/> is true, the complete subnode is removed. /// If recurse is specified removal fails if teh node has subnodes. /// </summary> /// <param name="path"></param> /// <param name="recurse">Indicats if the removal contains ths subnodes</param> /// <returns>true, if nod (and subnodes) has been removed, false otherwise</returns> public bool RemoveNode(HierarchyPath <TKey> path, bool recurse) { bool isLocked = false; try { this.writeLock.Enter(ref isLocked); if (path.IsRoot) { if (!recurse && this.rootNode.HasChildNodes) { // is recurse is not set, the root node can be exhanged if the root has no child nodes return(false); } this.rootNode = new ImmutableNode <TKey, TValue>(); return(true); } else { // this isn't a special case. // use the hierachy writer for inner nodes var writer = new RemoveNodeHierarchyWriter <TKey, ImmutableNode <TKey, TValue> >(); var resultRootNode = writer.RemoveNode(this.rootNode, path, recurse, out var nodeWasRemoved); if (!object.ReferenceEquals(resultRootNode, rootNode)) { this.rootNode = resultRootNode; } return(nodeWasRemoved); } } finally { if (isLocked) { this.writeLock.Exit(); } } }
/// <summary> /// Adds a value to the immutable hierarchy at the specified position. /// </summary> /// <param name="path">Specifies where to set the value</param> /// <param name="value">the value to keep</param> /// <returns>returns this</returns> public void Add(HierarchyPath <TKey> path, TValue value) { bool isLocked = false; try { this.writeLock.Enter(ref isLocked); var writer = new SetOrAddNodeValueWriter <TKey, TValue, ImmutableNode <TKey, TValue> >(createNode: key => new ImmutableNode <TKey, TValue>(key)); // if the root node has changed, it substitutes the existing root node. this.rootNode = writer.AddValue(this.rootNode, path, value); } finally { if (isLocked) { this.writeLock.Exit(); } } }
public void ImmutableNode_fails_on_replacing_unknown_child() { // ARRANGE var child = new ImmutableNode <string, int>("a"); var node = new ImmutableNode <string, int>(null).AddChild(child); var secondChild = new ImmutableNode <string, int>("b"); // ACT var result = Assert.Throws <InvalidOperationException>(() => node.ReplaceChild(child, secondChild)); // ASSERT Assert.Equal("Key of child to replace (key='a') and new child (key='b') must be equal", result.Message); Assert.True(node.HasChildNodes); Assert.Same(child, node.ChildNodes.Single()); var(found, addedChild) = node.TryGetChildNode("a"); Assert.True(found); Assert.Same(child, addedChild); }
/// <summary>節点の番号を返す</summary> /// <param name="node">節点オブジェクト</param> /// <returns>節点の番号</returns> private int getNodeIndex(ImmutableNode node) { for (int i = 0; i < nodes.Count; i++) { if (nodes[i].Equals(node)) return i; } return -1; }
/// <summary>節点を返す</summary> /// <param name="node">読み取り専用節点</param> /// <returns>節点</returns> private Node getNode(ImmutableNode node) { foreach (Node nd in nodes) { if (nd.Equals(node)) return nd; } return null; }
/// <summary>節点にエネルギーを設定する</summary> /// <param name="potential">エネルギー</param> /// <param name="node">節点</param> public void SetPotential(double potential, ImmutableNode node) { int index = getNodeIndex(node); if (index != -1) SetPotential(potential, index); }
/// <summary>外部の系への流出流量を節点に設定する</summary> /// <param name="externalFlow">外部の系への流出流量</param> /// <param name="node">節点</param> public void SetExternalFlow(double externalFlow, ImmutableNode node) { int index = getNodeIndex(node); if (index != -1) SetExternalFlow(externalFlow, index); }
/// <summary>境界条件ノードか否かを設定する</summary> /// <param name="isBoundaryNode">境界条件ノードか否か</param> /// <param name="node">節点</param> public void SetBoundaryNode(bool isBoundaryNode, ImmutableNode node) { int index = getNodeIndex(node); if (index != -1) SetBoundaryNode(isBoundaryNode, index); }
/// <summary>節点を削除する</summary> /// <param name="node">節点</param> public void RemoveNode(ImmutableNode node) { Node nd = getNode(node); //節点に接続されている流路を削除 ImmutableChannel[] cnls = nd.GetChannels(); foreach (ImmutableChannel cnl in cnls) DisconnectNodes(cnl); //リストから削除 nodes.Remove(nd); //IDを詰める foreach (Node nod in nodes) { if (nod.ID == (nodes.Count - 1)) nod.ID = nd.ID; } //イベント通知 if (NodeRemoveEvent != null) NodeRemoveEvent(this, new NodeEventArgs(nd)); }
protected override void Init(MyObjectBuilder_DefinitionBase builder) { base.Init(builder); var def = (MyObjectBuilder_BendyComponentDefinition)builder; #region Config Layer = def.Layer; if (string.IsNullOrWhiteSpace(Layer)) { MyDefinitionErrors.Add(def.Package, $"{nameof(BendyComponentDefinition)} {builder.GetId()} has {nameof(Layer)} that is null or whitespace", LogSeverity.Error); } var nodes = new ImmutableNode[def.Nodes?.Length ?? 0]; if (def.Nodes != null) { for (var i = 0; i < def.Nodes.Length; i++) { var n = def.Nodes[i]; var m = Matrix.CreateWorld(n.Position, n.Forward, n.Up); if (Vector3.IsZero(m.Forward)) { MyDefinitionErrors.Add(def.Package, $"{nameof(BendyComponentDefinition)} {builder.GetId()} node {i} has an invalid matrix forward", LogSeverity.Error); } if (Vector3.IsZero(m.Up)) { MyDefinitionErrors.Add(def.Package, $"{nameof(BendyComponentDefinition)} {builder.GetId()} node {i} has an invalid matrix up", LogSeverity.Error); } if (Math.Abs(m.Forward.Dot(m.Up)) > 1e-3f) { MyDefinitionErrors.Add(def.Package, $"{nameof(BendyComponentDefinition)} {builder.GetId()} node {i} has an invalid matrix", LogSeverity.Error); } nodes[i] = new ImmutableNode(m, n.Movable); } } var edges = new ImmutableEdge[def.Edges?.Length ?? 0]; if (edges.Length > 0 && nodes.Length == 0) { throw new Exception($"Component {builder.GetId()} has edges when it has no nodes. Unrecoverable."); } if (def.Edges != null) { for (var i = 0; i < def.Edges.Length; i++) { var e = def.Edges[i]; var n0 = MathHelper.Clamp((int)e.From, 0, nodes.Length - 1); var n1 = MathHelper.Clamp((int)e.To, 0, nodes.Length - 1); if (n0 != e.From) { MyDefinitionErrors.Add(def.Package, $"{nameof(BendyComponentDefinition)} {builder.GetId()} edge {i} refers to an invalid from", LogSeverity.Error); } if (n1 != e.To) { MyDefinitionErrors.Add(def.Package, $"{nameof(BendyComponentDefinition)} {builder.GetId()} edge {i} refers to an invalid to", LogSeverity.Error); } var bones = e.Bones?.Split(null).Where(x => !string.IsNullOrWhiteSpace(x)).ToArray(); edges[i] = new ImmutableEdge((uint)n0, (uint)n1, e.Mode, bones != null ? new ReadOnlyList <string>(bones) : null, e.Control1, e.Control2); } } Nodes = new ReadOnlyList <ImmutableNode>(nodes); Edges = new ReadOnlyList <ImmutableEdge>(edges); #endregion #region Bone Meta var tmp = new Dictionary <string, List <BoneEdgeMetadata> >(); for (var i = 0; i < Edges.Count; i++) { var e = Edges[i]; if (e.Bones == null || e.Bones.Count <= 0) { continue; } for (var index = 0; index < e.Bones.Count; index++) { var b = e.Bones[index]; List <BoneEdgeMetadata> src; if (!tmp.TryGetValue(b, out src)) { tmp.Add(b, src = new List <BoneEdgeMetadata>()); } src.Add(new BoneEdgeMetadata((uint)i, index / (float)(e.Bones.Count - 1), 1f)); } } BoneMetadata = tmp.ToDictionary(x => x.Key, x => { var totalBoneWeight = x.Value.Sum(y => y.EdgeWeight); var dest = new List <BoneEdgeMetadata>(x.Value.Count); foreach (var old in x.Value) { if (old.EdgeWeight < 0) { continue; } dest.Add(new BoneEdgeMetadata(old.Edge, old.EdgeFactor, old.EdgeWeight / totalBoneWeight)); } return((IReadOnlyList <BoneEdgeMetadata>) new ReadOnlyList <BoneEdgeMetadata>(dest)); }); #endregion #region Constraints Distance = def.Distance?.Immutable() ?? new ImmutableRange <float>(RailConstants.DefaultMinLength, RailConstants.DefaultMaxLength); if (Distance.Min > Distance.Max) { MyDefinitionErrors.Add(def.Package, $"{nameof(BendyComponentDefinition)} {builder.GetId()} distance has min > max", LogSeverity.Error); } PreferredDistance = def.PreferredDistance ?? ((Distance.Max + Distance.Min) / 2); MaxAngleDegrees = def.MaxAngleDegrees ?? RailConstants.DefaultMaxAngleDegrees; if (MaxAngleDegrees < 0) { MyDefinitionErrors.Add(def.Package, $"{nameof(BendyComponentDefinition)} {builder.GetId()} max angle is less than zero", LogSeverity.Error); } MaxGradeRatio = def.MaxGradeRatio ?? RailConstants.DefaultMaxGradeRatio; if (MaxGradeRatio < 0) { MyDefinitionErrors.Add(def.Package, $"{nameof(BendyComponentDefinition)} {builder.GetId()} max grade ratio is less than zero", LogSeverity.Error); } #endregion }
/// <summary>Circuit test 3</summary> /// <remarks>Calculating heat transfer through a wall</remarks> private static void circuitTest3() { Circuit circuit = new Circuit("Heat transfer network through wall"); //Add nodes to circuit network ImmutableNode[] nodes = new ImmutableNode[6]; nodes[0] = circuit.AddNode(new Node("Room 1", 0)); nodes[1] = circuit.AddNode(new Node("Plywood", 17.9)); nodes[2] = circuit.AddNode(new Node("Concrete", 232)); nodes[3] = circuit.AddNode(new Node("Air gap", 0)); nodes[4] = circuit.AddNode(new Node("Rock wool", 4.2)); nodes[5] = circuit.AddNode(new Node("Room 2", 0)); //Set boundary conditions (Room air temperatures). circuit.SetBoundaryNode(true, nodes[0]); circuit.SetBoundaryNode(true, nodes[5]); //Set air temperatures. circuit.SetPotential(20, nodes[0]); circuit.SetPotential(10, nodes[5]); for (int i = 1; i < 5; i++) circuit.SetPotential(10, nodes[i]); //Initialize wall temperatures to 10 C. //Connect nodes. ImmutableChannel channel01 = circuit.ConnectNodes(nodes[0], nodes[1], new Channel("Room 1-Plywood", 174, 1)); ImmutableChannel channel12 = circuit.ConnectNodes(nodes[1], nodes[2], new Channel("Plywood-Concrete", 109, 1)); ImmutableChannel channel34 = circuit.ConnectNodes(nodes[2], nodes[3], new Channel("Concrete-Air gap", 86, 1)); ImmutableChannel channel45 = circuit.ConnectNodes(nodes[3], nodes[4], new Channel("Air gap-Rock wook", 638, 1)); ImmutableChannel channel56 = circuit.ConnectNodes(nodes[4], nodes[5], new Channel("Rock wool-Room 2", 703, 1)); CircuitSolver cSolver = new CircuitSolver(circuit); cSolver.TimeStep = 3600; for (int i = 0; i < nodes.Length; i++) Console.Write(nodes[i].Name + " "); Console.WriteLine(); for (int i = 0; i < 24; i++) { cSolver.Solve(); Console.Write((i + 1) + "H : "); for (int j = 0; j < nodes.Length; j++) Console.Write(nodes[j].Potential.ToString("F1") + " "); Console.WriteLine(); } Console.Read(); }
/// <summary>Constructor</summary> /// <param name="node1">節点1</param> /// <param name="node2">節点2</param> /// <param name="channel">流路</param> public NodeConnectionEventArgs(ImmutableNode node1, ImmutableNode node2, ImmutableChannel channel) { this.node1 = node1; this.node2 = node2; this.channel = channel; }
public ImmutableNode <string> Visit(GenericInstantiationTypeReference genericInstantiationTypeReference) { var args = genericInstantiationTypeReference.GenericTypeArguments; if (args.Count == 1 && genericInstantiationTypeReference.TypeDefinition is TopLevelTypeReference topLevel && topLevel.Name == "Nullable`1" && topLevel.Namespace == "System") { return(new ImmutableNode <string>(args[0].Accept(this), "?", null)); } if (IsValueTuple(genericInstantiationTypeReference, minArgs: 2) && TryUseTupleSyntax(args, out var tupleSyntax)) { return(tupleSyntax); } var builder = (ImmutableNode <string>)null; var argumentsLeft = genericInstantiationTypeReference.GenericTypeArguments.Count; void BuildNext(string typeName) { var(name, arity) = NameUtils.ParseGenericArity(typeName); if (arity != 0) { if (argumentsLeft < arity) { throw new InvalidOperationException("Number of generic arguments provided does not match combined type arity."); } builder = new ImmutableNode <string>(null, ">", builder); for (var i = 0; i < arity; i++) { argumentsLeft--; builder = new ImmutableNode <string>( genericInstantiationTypeReference.GenericTypeArguments[argumentsLeft].Accept(this), i == 0 ? null : ", ", builder); } builder = new ImmutableNode <string>(null, "<", builder); } builder = new ImmutableNode <string>(null, name, builder); } var currentType = genericInstantiationTypeReference.TypeDefinition; for (; currentType is NestedTypeReference nested; currentType = nested.DeclaringType) { BuildNext(nested.Name); builder = new ImmutableNode <string>(null, ".", builder); } topLevel = currentType as TopLevelTypeReference ?? throw new InvalidOperationException("Nested types must be declared by either a top-level type or another nested type."); BuildNext(topLevel.Name); if (argumentsLeft != 0) { throw new InvalidOperationException("Number of generic arguments provided does not match combined type arity."); } return(AddNamespace(topLevel, builder)); }
private ImmutableNode <string> AddNamespace(TopLevelTypeReference topLevelTypeReference, ImmutableNode <string> name) { return(currentNamespace == topLevelTypeReference.Namespace || string.IsNullOrEmpty(topLevelTypeReference.Namespace) ? name : new ImmutableNode <string>(new ImmutableNode <string>(null, topLevelTypeReference.Namespace, null), ".", name)); }
/// <summary>接点への流量を取得する</summary> /// <param name="node">接点</param> /// <returns>接点への流量</returns> public double GetFlow(ImmutableNode node) { if (node == node1) return -GetFlow(); if (node == node2) return GetFlow(); else return 0; }
/// <summary>回路網テスト4</summary> /// <remarks>壁体の熱流計算(潜熱変化付き)</remarks> private static void circuitTest4() { Circuit circuit = new Circuit("潜熱蓄熱材を持つ床の熱流計算"); //節点追加 Initialize("", 0.350, 1600.0, mType); ImmutableNode[] nodes = new ImmutableNode[7]; nodes[0] = circuit.AddNode(new Node("室内", 0)); nodes[1] = circuit.AddNode(new Node("フレキシブルボード", 1600.0 * 0.0165)); nodes[2] = circuit.AddNode(new Node("スミターマル20C", 0)); nodes[3] = circuit.AddNode(new Node("発熱層", 0)); nodes[4] = circuit.AddNode(new Node("スミターマル30C", 0)); nodes[5] = circuit.AddNode(new Node("ロックウール", 84.0 * 0.065)); nodes[6] = circuit.AddNode(new Node("床下", 0)); //空気温度を境界条件とする circuit.SetBoundaryNode(true, nodes[0]); circuit.SetBoundaryNode(true, nodes[5]); //空気温度設定 circuit.SetPotential(20, nodes[0]); circuit.SetPotential(10, nodes[5]); //接続処理 ImmutableChannel channel01 = circuit.ConnectNodes(nodes[0], nodes[1], new Channel("室1-合板", 173.32, 1)); ImmutableChannel channel12 = circuit.ConnectNodes(nodes[1], nodes[2], new Channel("合板-コンクリート", 108.65, 1)); ImmutableChannel channel34 = circuit.ConnectNodes(nodes[2], nodes[3], new Channel("コンクリート-空気層", 128.86, 1)); ImmutableChannel channel45 = circuit.ConnectNodes(nodes[3], nodes[4], new Channel("空気層-ロックウール", 681.24, 1)); ImmutableChannel channel56 = circuit.ConnectNodes(nodes[4], nodes[5], new Channel("ロックウール-室2", 702.76, 1)); ImmutableChannel channel67 = circuit.ConnectNodes(nodes[5], nodes[6], new Channel("ロックウール-室2", 702.76, 1)); CircuitSolver cSolver = new CircuitSolver(circuit); cSolver.TimeStep = 3600; for (int i = 0; i < nodes.Length; i++) Console.Write(nodes[i].Name + " "); Console.WriteLine(); for (int i = 0; i < 24; i++) { cSolver.Solve(); Console.Write((i + 1) + "H : "); for (int j = 0; j < nodes.Length; j++) Console.Write(nodes[j].Potential.ToString("F1") + " "); Console.WriteLine(); } Console.Read(); }
/// <summary>回路網テスト3</summary> /// <remarks>壁体の熱流計算</remarks> private static void circuitTest3() { Circuit circuit = new Circuit("壁体の熱流計算"); //節点追加 ImmutableNode[] nodes = new ImmutableNode[6]; nodes[0] = circuit.AddNode(new Node("室1", 0)); nodes[1] = circuit.AddNode(new Node("合板", 17.9)); nodes[2] = circuit.AddNode(new Node("コンクリート", 232)); nodes[3] = circuit.AddNode(new Node("空気層", 0)); nodes[4] = circuit.AddNode(new Node("ロックウール", 4.2)); nodes[5] = circuit.AddNode(new Node("室2", 0)); //空気温度を境界条件とする circuit.SetBoundaryNode(true, nodes[0]); circuit.SetBoundaryNode(true, nodes[5]); //空気温度設定 circuit.SetPotential(20, nodes[0]); circuit.SetPotential(10, nodes[5]); for (int i = 1; i < 5; i++) circuit.SetPotential(10, nodes[i]); //壁体内温度は10℃均一とする //接続処理 ImmutableChannel channel01 = circuit.ConnectNodes(nodes[0], nodes[1], new Channel("室1-合板", 174, 1)); ImmutableChannel channel12 = circuit.ConnectNodes(nodes[1], nodes[2], new Channel("合板-コンクリート", 109, 1)); ImmutableChannel channel34 = circuit.ConnectNodes(nodes[2], nodes[3], new Channel("コンクリート-空気層", 86, 1)); ImmutableChannel channel45 = circuit.ConnectNodes(nodes[3], nodes[4], new Channel("空気層-ロックウール", 638, 1)); ImmutableChannel channel56 = circuit.ConnectNodes(nodes[4], nodes[5], new Channel("ロックウール-室2", 703, 1)); CircuitSolver cSolver = new CircuitSolver(circuit); cSolver.TimeStep = 3600; for (int i = 0; i < nodes.Length; i++) Console.Write(nodes[i].Name + ", "); Console.WriteLine(); for (int i = 0; i < 24; i++) { cSolver.Solve(); Console.Write((i + 1) + "H, "); for (int j = 0; j < nodes.Length; j++) Console.Write(nodes[j].Potential.ToString("F1") + ", "); Console.WriteLine(); } Console.Read(); }
/// <summary>節点を接続する</summary> /// <param name="node1">節点1</param> /// <param name="node2">節点2</param> /// <param name="channel">接続に使用する流路</param> /// <returns>接続した流路(Clone処理を行うため<paramref name="channel"/>とは異なる)</returns> public ImmutableChannel ConnectNodes(ImmutableNode node1, ImmutableNode node2, Channel channel) { //節点存在確認 Node nd1 = getNode(node1); Node nd2 = getNode(node2); if (nd1 == null || nd2 == null) return null; //流路を複製してIDを付与 Channel newChannel = (Channel)channel.Clone(); newChannel.ID = channels.Count; //接続処理 newChannel.Connect(nd1, nd2); //リストに追加 this.channels.Add(newChannel); //イベント通知 if (NodeConnectEvent != null) NodeConnectEvent(this, new NodeConnectionEventArgs(nd1, nd2, newChannel)); return newChannel; }
/// <summary>Constructor</summary> /// <param name="node">節点</param> public NodeEventArgs(ImmutableNode node) { this.node = node; }
public ImmutableNode(ImmutableNode <T> prev, T value, ImmutableNode <T> next) { Value = value; Prev = prev; Next = next; }