通过属性</t>动态排序List <t>

时间:2015-04-06 22:13:40

标签: c# .net

我知道已经提出了一些类似的问题,但大多数答案都依赖于Linq,我对此并不感兴趣(出于性能原因)。

我有兴趣看看是否有人有任何解决方案来动态排序具有在运行时确定的值的列表。到目前为止,我只能用if-else&#39; s / switch来提出一些东西。

以下是我使用的示例代码,并尝试使其正常工作。它是一个控制台应用程序,它由一个小模型类,一个Sorter类(实现IComparer)和Main()方法组成:

项目模型类

public class Project
{
    public int ProjID { get; set; }
    public string Title { get; set; }
    public string Owner { get; set; }
    public DateTime Created { get; set; }

    public override string ToString() 
    {
        return string.Format("--  ProjID: {0}, Title: {1}, Owner: {2}, Created on {3}", ProjID, Title, Owner, Created);
    }
}

比较方法

public class ProjectSorter : IComparer<Project>
{
    public string Prop { get; set; }

    // ctor, what do you want to sort by?
    public ProjectSorter(string prop)
    {
        Prop = prop;
    }
    public ProjectSorter() { }

    int IComparer<Project>.Compare(Project x, Project y)
    {
        if (this.Prop == "ProjID") {
            return x.ProjID.CompareTo(y.ProjID);
        }     
        else if (Prop == "Title")
        {
            return x.Title.CompareTo(y.Title);
        }
        else if (Prop == "Created")
        {
            return x.Created.CompareTo(y.Created);
        }
        // etc...

        return 0; // just to get this method working
    }
}

主要方法(测试比较器/分拣机)

static void Main(string[] args)
{
    List<Project> projectList = new List<Project>
    {
        new Project { ProjID = 1, Title = "First Proj", Owner = "rushfive", Created = DateTime.Now},
        new Project { ProjID = 2, Title = "2nd Proj", Owner = "rushfive", Created = DateTime.Now.AddDays(-2) },
        new Project { ProjID = 3, Title = "Amazing Redos", Owner = "waldrw", Created = DateTime.Now},
        new Project { ProjID = 4, Title = "Eat Lunch", Owner = "jedlow", Created = DateTime.Now.AddDays(-5)},
        new Project { ProjID = 5, Title = "Update Labs", Owner = "somsky", Created = DateTime.Now},
    };

    Console.WriteLine("\nListing Projects in DEFAULT ORDER:");
    foreach(Project p in projectList) 
    {
        Console.WriteLine(p);
    }

    Console.WriteLine("\nNow, Listing Projects sorted by Title:");

    IComparer<Project> comparer1 = new ProjectSorter("Title");
    projectList.Sort(comparer1);
    foreach (Project p in projectList)
    {
        Console.WriteLine(p);
    }

    Console.WriteLine("\nNow, Listing Projects sorted by CREATED DATE:");

    IComparer<Project> comparer2 = new ProjectSorter("Created");
    projectList.Sort(comparer2);
    foreach (Project p in projectList)
    {
        Console.WriteLine(p);
    }
}

鉴于我上面的当前实现,它可以动态地按属性正确地对列表进行排序..但是使用可能很长的if-else / switch逻辑。

有没有更好的方法来实现这种排序?一个常见的用例是用户点击浏览器中的某些内容,使列表按特定属性排序。

似乎使用一个巨大的if-else / switch可能是不必要的,但我不知道任何其他方式(想想更优雅和更少的代码)。也许我只是因为表达性的javascript与其他一些语言相比而有点被宠坏了。

关于这个问题的任何想法?

3 个答案:

答案 0 :(得分:1)

我会创建一个包含lambda的包装器来执行从Project到比较对象的转换。

请注意,这假设所有涉及的类型都实现IComparable<T>

public static class SortProject
{
    public static ProjectSorter<T> By<T>(Func<Project, T> transform)
    {
        return new ProjectSorter<T>(transform);
    }
}

public class ProjectSorter<T> : IComparer<Project>
                  where T : IComparable<T>
{
    private Func<Project, T> _transform;

    public ProjectSorter(Func<Project, T> transform)
    {
        _transform = transform;
    }

    public int Compare(Project left, Project right)
    {
        //Put null first in order, note that this can be changed based on what you want
        if (left == null) return right == null ? -1 : 0;
        if (right == null) return 1;

        return _transform(left).CompareTo(_transform(right));
    }
}

我认为使用静态方法C#可以在这里推断出正确的东西(没有测试,显式注释会起作用)。

例如:

projectList.Sort(SortProject.By(p => p.Title));

答案 1 :(得分:1)

<rant>首先,由于性能原因没有对Linq进行排序,我不相信它会有所作为。请显示Linq在您的应用程序中导致性能损失,内存压力或类似情况的地方,我会非常惊讶。 </rant>

所以,我们想要一个通用的排序方法,我们提供一个属性名称。如果我们可以提供表达式,那将更容易:

class PropertyComparer<T> : IComparer<T>
{
    private readonly Func<T, IComparable> _selector;

    public PropertyComparer(Func<T, IComparable> selector)
    {
        this._selector = selector;
    }

    public int Compare(T x, T y)
    {
        var left = this._selector(x);
        var right = this._selector(y);
        if (left == null)
        {
            if (right == null)
                return 0;
            else
                return -right.CompareTo(null);
        }
        else
        {
            return left.CompareTo(right);
        }
    }
}

否则,我们必须建立一个:

class PropertyComparer<T> : IComparer<T>
{
    private readonly Func<T, IComparable> _selector;

    public PropertyComparer(string propertyName)
    {
        var selectorParameter = Expression.Parameter(typeof (T), "x"); //parameter [T x]
        var property = Expression.PropertyOrField(selectorParameter, propertyName); // [x.Property]
        var cast = Expression.Convert(property, typeof(IComparable)); // [x.Property as IComparable]
        this._selector = Expression.Lambda<Func<T, IComparable>>(cast, selectorParameter).Compile();
    }

    public int Compare(T x, T y)
    {
        var left = this._selector(x);
        var right = this._selector(y);
        if (left == null)
        {
            if (right == null)
                return 0;
            else
                return -right.CompareTo(null);
        }
        else
        {
            return left.CompareTo(right);
        }
    }
}

它假定属性为IComparable,以便它可以进行排序。

答案 2 :(得分:0)

你可以使用这样的通用比较器:

/// <summary>
/// Generic comparer by key selector
/// </summary>
public class KeySelectorComparer<T, TProperty> : Comparer<T> where TProperty : IComparable<TProperty>
{
    private readonly Func<T, TProperty> keySelector;
    public KeySelectorComparer(Func<T, TProperty> keySelector)
    {
        if (keySelector == null)
            throw new ArgumentException("'keySelector' parameter can not be null.", "keySelector");

        this.keySelector = keySelector;
    }
    public bool Equals(T x, T y)
    {
        if (ReferenceEquals(x, y)) return true;

        if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
            return false;

        return this.keySelector(x).Equals(this.keySelector(y));
    }

    public int GetHashCode(T obj)
    {
        if (ReferenceEquals(obj, null)) return 0;

        return this.keySelector(obj).GetHashCode();
    }

    public override int Compare(T x, T y)
    {
        if (x == null)
        {
            if (y == null)
                return 0;

            return 1;
        }
        if (y == null)
            return -1;

        return this.keySelector(x).CompareTo(this.keySelector(y));
    }
}

所以你可以根据你想要的密钥选择器比较你想要的任何集合:

var list = new List<MyObj>();
list.Sort(new KeySelectorComparer<MyObj, int>(s=> s.Id));