从列表中删除项目及其所有引用

8
我面临这样一种情况,我有依赖对象,我希望能够删除一个对象及其所有引用。 假设我有以下代码中所示的对象结构,其中分支类型引用了两个节点。
public class Node
{
    // Has Some Data!
}

public class Branch
{
    // Contains references to Nodes
    public Node NodeA
    public Node NodeB
}

public class Graph
{
    public List<Node> Nodes;
    public List<Branch> Branches;
}

如果我从Graph类的Nodes列表中删除一个节点,则仍然可能存在一个或多个Branch对象仍包含对已删除节点的引用,从而使其保留在内存中。实际上,我希望将任何对已删除节点的引用设置为null,并让垃圾回收机制起作用。
除了枚举每个分支并按顺序检查每个节点引用之外,是否有任何聪明的想法可以在每个分支实例中删除对节点的引用,以及任何其他引用已删除节点的类?

1
你实际上在 Branch 上存储任何数据吗?如果没有,你可以完全摆脱那个类,并只在 Node 类上存储相关节点。 - Ian Mercer
嗨,是的,我正在存储一些其他数据,上面的数据结构只是一个简单的示例,用来展示我的实际模型的引用位置。 - LiamV
6个回答

6
目前C#语言没有内置的功能来实现此功能(您无法跟踪赋值)。您需要在某个地方跟踪所有引用,并在为其分配新引用时立即更新它。一个非常通用的想法是在节点本身提供“Removed”事件,并在对象被丢弃时引发该事件。每次您想要保留对Node的新引用时,您都可以使用匹配的委托订阅该事件,以将对该对象的引用设置为null。当然,如果您正在使用一组先前已知的类型以特定方式引用节点,则可能存在更简单和更有效的方法来完成任务。

1

将您的节点更改为包括其所在分支列表:

public class Node
{
    // Has Some Data!

    public List<Branch> BranchesIn;
    public List<Branch> BranchesOut;  // assuming this is a directed graph

    public void Delete()
    {
      foreach (var branch in BranchesIn)
        branch.NodeB.BranchesOut.Remove(branch);

      foreach (var branch in BranchesOut)
        branch.NodeA.BranchesIn.Remove(branch);

      BranchesIn.Clear();
      BranchesOut.Clear();
     }
}

public class Branch
{
    // Contains references to Nodes
    public Node NodeA
    public Node NodeB
}

现在你的 Graph 类不需要节点或分支列表,它只需要一个根节点。当你移除一个节点时,所有连接到它的分支都将被删除。显然,你封装了所有添加和移除节点和分支的方法,以便外部代码无法破坏结构。

如果你实际上没有在分支(通常称为边)上存储任何数据,那么你根本不需要它。节点可以仅维护它们链接到和链接出来的其他节点的列表。


你说得对,根据我提供的信息,我确实不需要一个节点或分支的列表,但还有其他因素你可能不知道要考虑。例如,这些数据实际上将存储在数据库中,因此更适合拥有每种类型的列表,这些列表与数据库表相对应。 - LiamV
这个结构可以很容易地映射到数据库:Nodes 的表、Edges 的表以及它们之间的外键关系。这是一个 100% 的关系模型。EF 4 可以处理围绕着惰性加载边缘和节点的所有问题,而不需要额外的工作。当你将数据库导入实体数据模型时,你将自动免费获取 Node.Edges。事实上,当你删除一个 Edge 时,它也会很容易,因为它可以自动从每个节点中删除。 - Ian Mercer

0

您可以通过查询分支元素以获取对要删除的每个节点的引用,就像这个示例一样:

class Branch
{
    public Branch(Node nodeA, Node nodeB) { NodeA = nodeA; NodeB = nodeB; }
    public Node NodeA { get; set; }
    public Node NodeB { get; set; }
}

class Node
{
    public Node(string name) { Name = name; }
    public string Name { get; set; }
}

...

List<Node> nodes = new List<Node>() { new Node("Apple"), new Node("Banana") };
List<Branch> branches = new List<Branch>() { new Branch(nodes[0], nodes[1]), new Branch(nodes[1], nodes[0]) };

Node node = nodes[0];
nodes.Remove(node);

var query = from branch in branches
            where branch.NodeA == node || branch.NodeB == node 
            select branch;

foreach (Branch branch in query)
{
    if (branch.NodeA == node)
        branch.NodeA = null;
    if (branch.NodeB == node) // could just be 'else' if NodeA cannot equal NodeB
        branch.NodeB = null;
}

这对于从分支列表中删除引用是可以的。然而,正如Mehrdad指出的那样,如果对Node对象的引用更加普遍,那么消除所有引用就变得越来越困难。


0
某些持有节点引用的类不希望有机制删除此引用。并且,没有其他方法。您必须手动迭代并将它们设置为 null。但是,如果节点表示有限或内存密集型资源,则应考虑更好地管理对其的访问,可能是在中央位置。

非常感谢您所有的回复,这确实给了我很多思考。我想我会采取Hightechrider建议的方法,直到我遇到问题。出于这个原因,我已经接受了他的答案。 - LiamV

0

尝试使用WeakReference作为Node或Branch的包装器,列表将包含这些弱引用。


1
“弱引用”可以消除垃圾收集问题,但如果需要在不同的位置适当更新数据结构以及删除节点后,则无法解决该问题。 - Mehrdad Afshari
因此,实现OnAdded、OnRemoved事件并保持向后引用 - 这样每个节点都将知道它所在的分支。 - Karel Frajták

0
我建议让只有你的图知道分支和节点。这样,你就可以控制访问并确保自己知道如何使所有引用无效。如果需要在节点上提供用户数据的访问权限,可以提供迭代结构的方法,而不是直接访问原始结构。你可以通过泛型将用户信息嵌入结构类中(例如为每个节点和每个分支定义一个用户定义的“标签”属性)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接