在类似树的通用列表中以字母顺序对子项进行排序的扩展方法

时间:2013-12-12 14:30:39

标签: c# linq generics tree extension-methods

我们的想法是制作扩展方法,用于对任何树(如示例中列出的父子结构)的子项(孩子的孩子等)进行排序。 另外重要的是要说排序的属性只在运行时出现(就像列表本身一样)。

扩展方法应该与此签名类似:

public static IEnumerable<T> OrderChildren<T>(
this IEnumerable<T> source,
Func<T, IEnumerable<T>> childrenSelector,
Func<T, string> orderSelector)
{
...
}

以下是创建列表的示例类:

public class Example
{
    public IEnumerable<Example> Children;

    public string Name;

    public int Id;

    public Example Parent;
}

一些模拟数据:

{
   ...
   var collection = MockUp();
   ...
}

private static IEnumerable<Example> MockUp()
{
    var rootCollection = new List<Example>();
    var firstLevelCollectionA = new List<Example>();
    var firstLevelCollectionB = new List<Example>();
    var secondLevelCollectionAA = new List<Example>();
    var secondLevelCollectionAB = new List<Example>();
    var secondLevelCollectionBA = new List<Example>();
    var secondLevelCollectionBB = new List<Example>();

    secondLevelCollectionAA.Add(new Example() {Name = "SecondLvlAA1"});
    secondLevelCollectionAA.Add(new Example() {Name = "SecondLvlAA2"});

    secondLevelCollectionAB.Add(new Example() {Name = "SecondLvlAB1"});
    secondLevelCollectionAB.Add(new Example() {Name = "SecondLvlAB2"});

    secondLevelCollectionBA.Add(new Example() {Name = "SecondLvlBA1"});
    secondLevelCollectionBA.Add(new Example() {Name = "SecondLvlBA2"});

    secondLevelCollectionBB.Add(new Example() {Name = "SecondLvlBB1"});
    secondLevelCollectionBB.Add(new Example() {Name = "SecondLvlBB2"});

    firstLevelCollectionA.Add(new Example() {Name = "FirstLvlA1", Children = secondLevelCollectionAA});
    firstLevelCollectionA.Add(new Example() {Name = "FirstLvlA2", Children = secondLevelCollectionAB});

    firstLevelCollectionB.Add(new Example() {Name = "FirstLvlB1", Children = secondLevelCollectionBA});
    firstLevelCollectionB.Add(new Example() {Name = "FirstLvlB2", Children = secondLevelCollectionBB});

    rootCollection.Add(new Example() {Name = "Root1", Children = firstLevelCollectionA});
    rootCollection.Add(new Example() {Name = "Root2", Children = firstLevelCollectionB});

    return rootCollection;
}

然后我们需要调用扩展方法OrderChildren,如下所示:

collection.OrderChildren(x => x.Children, x => x.Name)

那么对这种扩展方法的主体有什么建议吗?

1 个答案:

答案 0 :(得分:0)

这是一个有趣的问题。在保持性感的同时,我没有想办法如何在没有某种反射的情况下做到这一点。问题主要在于动态设置子属性。

public static IEnumerable<T> OrderRecursively<T, TKey>(
    this IEnumerable<T> source,
    Expression<Func<T, IEnumerable<T>>>  childrenExpression,
    Func<T, TKey> keySelector)
{
    if (source == null)
        throw new Exception("source");

    var memberExpression = childrenExpression.Body as MemberExpression;
    if (memberExpression == null)
        throw new Exception("memberExpression");

    var propertyInfo = memberExpression.Member as PropertyInfo; 
    if(propertyInfo == null)
        throw new Exception("propertyInfo");

    var instance = Expression.Parameter(typeof (T), "instance");
    var parameter = Expression.Parameter(typeof (IEnumerable<T>), "param");

    var childrenSet = Expression.Lambda<Action<T, IEnumerable<T>>>(
        Expression.Call(instance, propertyInfo.GetSetMethod(), parameter),
        new[] {instance, parameter}).Compile();

    return OrderRecursivelyReflectionDarkness(
        source, 
        childrenExpression.Compile(), 
        keySelector, 
        childrenSet);
}

private static IEnumerable<T> OrderRecursivelyReflectionDarkness<T, TKey>(
    this IEnumerable<T> source,
    Func<T, IEnumerable<T>>  childrenSelector,
    Func<T, TKey> keySelector,
    Action<T, IEnumerable<T>> childrenSetter)
{
    foreach (var parent in source.OrderBy(keySelector))
    {
        var children = childrenSelector(parent);
        if (children != null && children.Any())
        {
            var sortedChildren = children.OrderRecursivelyReflectionDarkness(
                childrenSelector,
                keySelector,
                childrenSetter);

            childrenSetter(parent, sortedChildren);
            yield return parent;
        }
    }
}

你需要照顾memberExpression.Member,因为它也可以是字段。

也可以避免反射,但使用它会更加尴尬。

public static IEnumerable<T> OrderRecursively<T, TKey>(
    this IEnumerable<T> source,
    Func<T, IEnumerable<T>>  childrenSelector,
    Func<T, TKey> keySelector,
    Action<T, IEnumerable<T>> childrenSet)
{
    if (source == null)
        throw new Exception("source");

    return OrderRecursivelyReflectionDarkness(
        source, 
        childrenSelector, 
        keySelector, 
        childrenSet);
}

//and usage
collection = collection.OrderRecursively(
x => x.Children, 
x => x.Name, 
(parent, sortedChildren) => parent.Children = sortedChildren).ToList();