使用Linq检测循环依赖,字符串属性

时间:2015-06-08 20:57:59

标签: c# linq

想象一下像这样的对象

public class ContentType
{
    public string Alias { get; set;}
    public string ParentAlias { get; set;}
}

这些物品的扁平集合

List<ContentType> contentTypes...;

如何使用linq链式语法查询来确定集合中是否存在循环引用。

//Example
ContentType #50
Alias: Truck
ParentAlias: Vehicle

ContentType #90
Alias: Vehicle
ParentAlias: Truck

这将是一个循环依赖,它将破坏创建内容类型的代码(它将陷入无限循环中走父层次结构......)

因此,在处理父/子内容类型之前,我想首先检测集合中是否存在循环依赖关系,并在检测到操作时暂停操作。

1 个答案:

答案 0 :(得分:7)

所以我们将从两个辅助方法开始。首先,我们需要一个方法来生成项目的所有祖先,当给定该项目和获取项目的父项的委托时:

public static IEnumerable<T> Ancestors<T>(T item, Func<T, T> parentSelector)
{
    while (item != null)
    {
        item = parentSelector(item);
        yield return item;
    }
}

我们还将编写一种方法,通过存储所有先前生成的项目并查看每个新项目是否在该集合中来确定序列是否重复:

public static bool Repeats<T>(
    this IEnumerable<T> sequence,
    IEqualityComparer<T> comparer = null)
{
    comparer = comparer ?? EqualityComparer<T>.Default;
    var set = new HashSet<T>(comparer);
    foreach (var item in sequence)
        if (!set.Add(item))
            return true;
    return false;
}

从这里我们可以通过计算每个项目的祖先并确定是否有任何重复来确定任何序列是否包含循环:

public static bool ContainsCycles<T>(IEnumerable<T> sequence,
    Func<T, T> parentSelector,
    IEqualityComparer<T> comparer = null)
{
    comparer = comparer ?? EqualityComparer<T>.Default;
    return sequence.Any(item => Ancestors(item, parentSelector).Repeats(comparer));
}

剩下的就是编写一个计算每个项目的父项的方法,因为这不是您的类已经支持的操作,这可以通过从项目的别名创建查找然后使用它来完成: / p>

IEnumerable<ContentType> types = CreateContentTypes();
var lookup = types.ToDictionary(type => type.Alias);
bool anyCycles = ContainsCycles(types, type => lookup[type.ParentAlias]);

就性能和可能的改进而言,如果您正在处理特别大的树/子树,则可以缓存中间计算的结果。例如,如果我们有一个节点A,一个父B和一个祖父C,它是一个根,那么在进行计算以确定A是否在一个循环中时,我们还需要确定B是否在一个循环中。如果我们已经确定B是否在一个周期之前,并缓存它,我们可以跳过这一步。如果我们没有,那么我们可以在为A做循环计算器时缓存它,然后我们不需要在稍后检查B时再次执行它。

这确实使代码复杂化,所以如果你没有特别大/深的图,你可以选择不打扰缓存这些中间结果,只选择为每个项重新计算它们:

public static bool IsInCycle<T>(
    this IEnumerable<T> sequence,
    HashSet<T> itemsNotInASequence,
    IEqualityComparer<T> comparer = null)
{
    comparer = comparer ?? EqualityComparer<T>.Default;
    var set = new HashSet<T>(comparer);
    foreach (var item in sequence)
    {
        if (itemsNotInASequence.Contains(item))
            return false;
        else if (!set.Add(item))
            return true;
    }
    itemsNotInASequence.UnionWith(set);
    return false;
}

public static bool ContainsCycles<T>(IEnumerable<T> sequence,
    Func<T, T> parentSelector,
    IEqualityComparer<T> comparer = null)
{
    comparer = comparer ?? EqualityComparer<T>.Default;
    var itemsNotInASequence = new HashSet<T>(comparer);
    return sequence.All(item => Ancestors(item, parentSelector)
        .IsInCycle(itemsNotInASequence));
}