如何使用LINQ返回对象的祖先?

6

我有一个District类,看起来像这样:

public class District
{
    public int Id { get; set; }
    public string Name { get; set; }
    public District Parent { get; set; }
    public IEnumerable<District> Ancestors { get { /* what goes here? */ } }
}

我希望能够获得每个地区的祖先列表。例如,如果地区“1.1.1”是地区“1.1”的子级,而“1.1”又是“1”的子级,则获取“1.1.1”地区的祖先将返回一个列表,其中包含名称为“1.1”和“1”的地区对象。

这是否涉及到yield return语句(我从未完全理解过)?它可以一行完成吗?

3个回答

14

只要那一行够长,什么都可以用一行完成 :)

在这种情况下,最简单的做法可能不是用一行完成:

public IEnumerable<District> Ancestors
{
    get
    {
        District parent = Parent;
        while (parent != null)
        {
            yield return parent;
            parent = parent.Parent;
        }
    }
}

如果你想更好地理解yield return,这里有一个建议——C# in Depth第一版的第6章仍然可以免费获取,其中详细介绍了C# 2中的迭代器。从第一版页面上获取。


循环引用怎么办? - Elijah Glover
@Elijah:我假设没有循环引用。如果有,你需要保持一个集合并执行以下操作:while (parent != null && !visitedParents.Contains(parent)) - Jon Skeet
确实没有循环引用,所以这个完美地运作了。我最终需要在列表中包含当前地区,但你的代码非常易于阅读和修改。谢谢。我也会看一下你的书! - Chris

2
您可能想考虑这个替代方案...它不是很"纯粹",但是可以大规模地有效地查询这种东西。(无论使用SQL还是不使用) SQL选择行的后代 请查看"已接受"的答案。
我唯一要添加的就是在标签之间添加分隔符。
哦,然后使用linq进行查询。
ancestors = Districts.Where( d => 
       d.Pedigree.Contains(Id) && 
       d.Pedigree.Length < Pedigree)
       .ToList();

“对于大多数情况,如果您正在使用ORM,这将导致在尝试迭代树时只有一个查询,而不是许多查询。”
“实际上,在单个父级的情况下,这甚至更容易,因为您的谱系将包含您的祖先,但如果您有分支谱系..... :)”

1
这是一个通用的扩展方法,用于获取基于接受的答案的对象祖先集合。
static public IEnumerable<T> GetAncestors<T>(this T source, Func<T, T> parentOf)
{
    var Parent = parentOf(source);
    while (Parent != null)
    {
        yield return Parent;
        Parent = parentOf(Parent);
    }
}

所以我可以在我的应用程序中使用它来处理所有层次结构:

public IEnumerable<District> Ancestors
{
    get
    {
        return this.GetAncestors(d => d.Parent);
    }
}

或者展开给定TreeView节点的所有父节点...

Node.GetAncestors(node => node.Parent).ToList().ForEach(n => n.Expanded = true);

这真的很方便。


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