【问题标题】:Removing items from lists and all references to them从列表中删除项目以及对它们的所有引用
【发布时间】:2010-04-27 20:16:24
【问题描述】:

我面临的情况是我有依赖对象,我希望能够删除一个对象以及对它的所有引用。

假设我有一个类似于下面代码的对象结构,其分支类型引用了两个节点。

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 类的节点列表中删除一个节点,一个或多个分支对象仍然可能包含对已删除节点的引用,从而将其保留在内存中,而我真正想要的将所有对已删除节点的引用设置为 null 并让垃圾收集开始。

除了枚举每个分支并按顺序检查每个节点引用之外,关于如何删除每个分支实例中的节点引用以及实际上引用已删除节点的任何其他类是否有任何聪明的想法?

【问题讨论】:

  • 您实际上是否在分支上存储任何数据?如果没有,您可以完全摆脱该类,只需将相关节点存储在 Node 类中。
  • 嗨,是的,我正在存储一些其他数据,上面的数据结构只是一个简单的示例,用于显示我的实际模型的引用在哪里。

标签: c# list reference


【解决方案1】:

没有内置的 C# 语言功能可以促进这一点(您无法真正跟踪作业)。您必须在某处跟踪所有引用,并在为其分配新引用后立即对其进行更新。一个非常普遍的想法是在Node 本身中提供Removed 事件,并在应该放弃对象时引发事件。每次您想保留对Node 的新引用时,您都需要使用匹配的委托订阅该事件,该委托会取消对该对象的引用。 当然,如果您使用一组以前已知的以特定方式引用节点的类型来执行此操作,则可能有更简单、更有效的方法来完成任务。

【讨论】:

    【解决方案2】:

    更改您的节点以包含其所在分支的列表:

    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 类不需要节点或分支列表,它只需要一个根节点。当你删除一个节点时,你会删除它的所有分支。显然,您封装了所有添加和删除节点和分支的方法,因此外部代码不会破坏结构。

    如果您实际上没有在分支(通常称为边缘)上存储任何数据,则根本不需要它。节点可以只维护它们链接的其他节点的列表。

    【讨论】:

    • 您说得对,根据我提供的信息,我不需要节点或分支列表,但您可能不知道要考虑其他因素。例如,这些数据实际上将存储在数据库中,因此将每种类型的列表映射到数据库表更合适。
    • 这种结构可以轻松地映射到数据库:节点表、边表、它们之间的外键关系。它是 100% 的关系模型。 EF 4 可以处理所有关于延迟加载边缘和节点的问题,而无需任何额外工作。当您将数据库导入实体数据模型时,您会自动免费获得 Node.Edges。事实上,它也会使删除变得容易,当您删除 Edge 时,它​​会自动从每个节点中删除它。
    【解决方案3】:

    如果某些机制只是删除此引用,则某些持有对 Node 的引用的类不会喜欢它。不,没有其他办法。您必须手动迭代并将它们设置为 null。但是,如果 Node 代表有限或内存密集型资源,您应该考虑更好地管理对它的访问,也许是在一个中心位置。

    【讨论】:

    • 非常感谢您的所有回复,这确实让我有了一些思考。我想我会沿着 Hightechrider 建议的路线走,直到遇到一些问题。出于这个原因,我接受了他的回答。
    【解决方案4】:

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

    【讨论】:

    • WeakReference 将消除垃圾收集问题,但如果您需要在删除节点后立即在不同位置适当地更新数据结构,它不会解决问题。
    • 所以实现事件 OnAdded、OnRemoved 并保持向后引用 - 这样每个节点都会知道它所在的分支。
    【解决方案5】:

    您当然可以查询您的分支元素以获取对您要删除的每个节点的引用,例如这个示例

    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 对象的引用更多,那么清除所有引用将变得越来越困难。

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-09-10
        • 2020-11-10
        • 2013-04-20
        • 1970-01-01
        • 2014-09-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多