C# 层次对象与自动夹具

C# 层次对象与自动夹具,c#,hierarchy,circular-reference,autofixture,C#,Hierarchy,Circular Reference,Autofixture,我实现了一个用于存储标记的类,标记集合必须是分层的,因此我的类是: public class Tag { public int Id { get; set; } public int Description { get; set; } public Tag ParentTag { get; set; } // … (methods for get children, add and remove children, etc.) } 这样,根标记(用户希望能够有许

我实现了一个用于存储标记的类,标记集合必须是分层的,因此我的类是:

public class Tag
{
    public int Id { get; set; }
    public int Description { get; set; }
    public Tag ParentTag { get; set; }
    // … (methods for get children, add and remove children, etc.)
}
这样,根标记(用户希望能够有许多独立的树)没有父标记,而非根标记必须有父标记

  • 这是实现层次结构的好方法吗?我发现了复合模式,但在我的领域中,所有的标记都是简单的标记,对领域专家来说,父标记和子标记之间没有区别

  • 测试中使用自动夹具时出现的问题;当我需要创建一个简单标记时,它会引发以下错误:

    失败:
    Ploeh.AutoFixture.ObjectCreationException
    :AutoFixture无法创建类型为
    Ploeh.AutoFixture.Kernel.SeedRequest
    的实例,因为遍历的对象图包含循环引用

  • 编辑: 我读过,但情况不同:我只有一个类,而不是2个,我不希望autofixture创建树,而只创建一个节点

    这是实现层次结构的好方法吗

    我看到了三个问题,一个小问题,一个更严重的问题,还有一个在你的具体情况下明显有问题

    潜在问题:

    1。让我们从一个小问题开始,它是关于属性名称与其类型之间的关系。我建议名为
    ParentTag
    的属性本身应为
    Tag
    类型。您将其声明为
    int
    (就像您对
    Id
    所做的那样)这一事实表明,您应该调用属性
    ParentTagId
    ,或者将属性的类型更改为
    Tag

    2.现在来谈谈更严重的问题。我认为
    Desc
    指向一个立即子标记。(如果一个标记可以有多个子标记,那么显然您为此属性选择了错误的类型。您需要某种类型的集合。但这是另一个问题。)

    如果您没有注意,同时存储父链接和子链接很容易导致不一致。因此,最好不要为每个标记提供双向链接,而只存储指向一个方向的链接

    然而,这将使以相反方向遍历层次结构变得复杂。解决这个问题的一种方法是存储独子链接;如果你想找到T的父标记,你首先要递归地遍历从根标记开始的层次结构,并持续跟踪你所走的“路径”,从而找到T;然后,父项将是路径中倒数第二个标记

    3.现在开始最直接的问题。例外情况提示:

    Ploeh.AutoFixture.ObjectCreationException
    […],因为遍历的对象图包含循环引用

    使用当前的
    标记实现
    ,可以构建包含循环的标记层次结构。我想你不会想要的

    例如,标记C可以将p作为其父标记,尽管p已经是C的子标记。因此,如果您开始遵循从C开始的
    ParentTag
    链,您将首先到达p,然后最终返回C,如果继续,您将发现自己陷入无限循环

    我不知道AutoFixture,但由于类似的原因,它似乎无法处理具体的标记层次结构

    你应该建立你的标签层次结构,“非循环”是这里的重要一点。但是,使用当前的
    标记
    类,您可以构建任何;它不能保证不会有任何循环

    防止循环标记层次结构的方法:

    1。
    ParentTag
    setter中执行周期检查:

    public Tag ParentTag
    {
        …
        set
        {
            if (!IsOrIsAncestorOf(value))
            {
                parentTag = value;
            }
            else
            {
                throw new ArgumentException("ParentTag", "would cause a cycle");
            }
        }
    }
    private Tag parentTag;
    
    private bool IsOrIsAncestorOf(Tag other)
    {
        return this == other || IsOrIsAncestorOf(other.Parent));
        //     ^^^^^^^^^^^^^    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //          Is   …   Or    …    IsAncestorOf
    }
    
    2.更简单的是,将
    ParentTag
    设置为只读
    ,这将强制您在构造函数中设置它。这将自动使构建循环标记层次结构变得不可能-如果您不相信,请尝试:

    public Tag(Tag parentTag)
    {
        this.parentTag = parentTag;
    }
    
    private readonly Tag parentTag;
    
    public Tag ParentTag
    {
        get
        {
            return parentTag;
        }
    }
    

    我建议使用第二种解决方案。

    对不起,我编辑类更改了ParentTag类型,int版本是错误的。Desc属性用于描述,编辑为“问题更新”以回答“复制我的标记”seemann@MarkSeemann你能移除重复的标签吗?谢谢,还有,在构造函数中添加一个参数,我再也不能创建一个假的Moqsolved了。添加一个空的构造函数来创建根标记,正如你所建议的,第二个解决方案是:简单而简洁地写“解决这个问题的一个方法是只存储子链接”,但这样我怎么能移动标记呢?即使现在,我也必须从private field中删除readonly属性,以便能够在Move()方法中更改它。我认为我们无法在评论部分解决这个问题。但基本上,您可以通过重建层次结构来移动标记。这听起来比实际情况更糟;毕竟,你可以“回收”未受影响的零件。