• (转)逐步为对象集合构建一个通用的按指定属性排序的方法


    原文:http://topic.csdn.net/u/20090407/13/371533da-f709-4f1f-bda9-b4a18060e713.html?seed=924471686

    有时候我们需要对集合中的自定义对象进行排序,以最原始的 System.Array 为例,如

    Person[] people = new Person[]{
    new Person(3, "Andy", new DateTime(1982, 10, 3)),
    new Person(1, "Tom", new DateTime(1993, 2, 10)),
    new Person(2, "Jerry", new DateTime(1988, 4, 23))
    };
    类 Person 的定义为:
    class Person
    {
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }

    public Person(int id, string name, DateTime birthday)
    {
    Id
    = id;
    Name
    = name;
    Birthday
    = birthday;
    }

    public override string ToString()
    {
    return String.Format("Id: {0,-6}Name: {1,-20}Birthday: {2:yyyy-MM-dd}", Id, Name, Birthday);
    }
    }
    可能会需要根据 Id、Name 及 Birthday 进行排序。在 .NET 中,自定义对象数组排序最常见的实现方式是在对象中实现 IComparable 接口,然后调用 Array.Sort(array) 静态方法,显然,在上述情形下,这不是一个好的解决办法。其实 .NET 提供了一个泛型的 Sort() 静态方法,可以根据指定的谓词函数进行排序,其定义如下:
    public static void Sort<T>(
    T[] array,
    Comparison
    <T> comparison
    )
    按照定义,我们先定义一个谓词函数:
    static int CompareById(Person first, Person second)
    {
    if (first.Id > second.Id)
    return 1;
    if (first.Id == second.Id)
    return 0;
    return -1;
    }
    然后在排序时,如下调用:
    Array.Sort(people, new Comparison<Person>(CompareById));
    使用语句输出结果:
    foreach (Person p in people)
    Console.WriteLine(p);
    可以看到 Person 数组已经按照 Id 排序了。因为 .NET 内置的类型大多都实现了 IComparable 接口,包括值类型,所以上面的谓词函数可以简化为:
    static int CompareById(Person first, Person second)
    {
    return first.Id.CompareTo(second.Id);
    }
    虽然这个函数写起来很简单,但是对每个需要进行排序的属性都写一个函数,也挺麻烦,幸好 .NET 2.0 提供了匿名委托,不用再单独定义函数了:
    Array.Sort(people, delegate(Person first, Person second){
    return first.Id.CompareTo(second.Id);
    });
    简单了许多,如果是 .NET 3.5,可以用 Lamda 表达式进一步简化:
    Array.Sort(people, (first, second) => first.Id.CompareTo(second.Id));
    在实际应用开发中,从性能和易用性上来说,到这一步大多数情形下已经足够。下面的部分可能有过度设计的嫌疑,但这里主要是研究 .NET 一些特性的使用,所以我们继续往下。
    能否直接返回一个委托,使我们不必关心 Person 类的具体属性比较,而直接根据属性进行排序呢?答案是肯定的。为 Person 类添加一个静态方法:
    public static Comparison<Person> CompareByProperty(string name)
    {
    switch (name)
    {
    case "Id":
    return (first, second) => first.Id.CompareTo(second.Id);
    case "Name":
    return (first, second) => first.Name.CompareTo(second.Name);
    case "Birthday":
    return (first, second) => first.Birthday.CompareTo(second.Birthday);
    default:
    throw new Exception("属性 " + name + " 不存在。");
    }
    }
    排序时,可以这样调用:
    Array.Sort(people, Person.CompareByProperty("Birthday"));
    还行,但是如果为 Person 类增加了新的属性,如果要按照新属性排序,必须要修改代码,能不能做到增加新属性而不修改代码呢?当然可以。因为要用到反射,为简化代码,突出主题,我们 假定所有使用到的属性都实现了 IComparable 接口,修改上面的 CompareByProperty(string) 方法为:
    public static Comparison<Person> CompareByProperty(string name)
    {
    Type typeOfPerson
    = typeof(Person);
    PropertyInfo p
    = typeOfPerson.GetProperty(name);
    if (p == null)
    throw new Exception("属性 " + name + " 不存在。");
    // 假定该类所有的属性均实现了接口 IComparable
    return (first, second) => ((IComparable)p.GetValue(first, null)).CompareTo(p.GetValue(second, null));
    }
    因为方法的签名仍保持一致,所以调用的语句不用修改。
    仔细观察上面的代码,应该可以把它的应用再扩大化,而不仅限于 Person 类,而这显然是泛型的长项。当然,这样的话,不应再把这个方法放在 Person 类中,我们暂时先把它移到主程序中,稍后再为它寻找一个好的归宿,修改后的CompareByProperty 泛型方法代码如下:
    public static Comparison<T> CompareByProperty<T>(string name)
    {
    Type typeOfPerson
    = typeof(T);
    PropertyInfo p
    = typeOfPerson.GetProperty(name);
    if (p == null)
    throw new Exception("属性 " + name + " 不存在。");
    // 假定该类所有的属性均实现了接口 IComparable
    return (first, second) => ((IComparable)p.GetValue(first, null)).CompareTo(p.GetValue(second, null));
    }
    调用时需要指定泛型参数:
    Array.Sort(people, CompareByProperty<Person>("Name"));
    到这里通用性已经很不错了,能否再更进一步呢?下面就是这篇文章所要抵达的终点:为 System.Array 类增加一个通用的按元素对象属性排序的方法,.NET 3.5 中新增了扩展方法,可以在不修改原有类代码的前提下为类增加新的实例方法,这正是我们这里所需要的,这需要新增加一个静态类,完整的代码如下:
    static class ExtensionArray
    {
    public static void SortBy(this Array array, string name)
    {
    Type elementType
    = array.GetType().GetElementType();
    Type bridge
    = typeof(Bridge<>).MakeGenericType(elementType);
    MethodInfo sortMethod
    = bridge.GetMethod("Sort");
    sortMethod.Invoke(
    null, new object[] { array, name });
    }

    private static class Bridge<T>
    {
    private static Comparison<T> CompareByProperty(string name) //不必再是泛型方法
    {
    Type typeOfPerson
    = typeof(T);
    PropertyInfo p
    = typeOfPerson.GetProperty(name);
    if (p == null)
    throw new Exception("属性 " + name + " 不存在。");
    // 假定该类所有的属性均实现了接口 IComparable
    return (first, second) => ((IComparable)p.GetValue(first, null)).CompareTo(p.GetValue(second, null));
    }

    public static void Sort(Array array, string name)
    {
    Array.Sort((T[])array, CompareByProperty(name));
    }
    }
    }
    注意,在上面的代码中增加了一个私有的嵌套类 Bridge,这主要是为了便于调用 Array.Sort <T>() 泛型方法,如果没有这个类进行过渡,则必须使用大量的反射方法才能调用 Array.Sort <T> 方法。
    现在按属性排序只需这样调用:
    people.SortBy("Birthday");
    代码很简单,但是我们应当看到,通用性的扩展是以牺牲性能为代价的。尤其是在后期引入反射以后,性能大幅下降,简单测试了一下,Array.Sort(people, (first, second) => first.Id.CompareTo(second.Id)) 与 people.SortBy("Id") 性能相差约为 120 倍。所以在实际应用中,我们应把握好度,适可而止。但是从学习的角度上来说,我觉得在自己能力范围内尽量深入,还是很有价值的。
  • 相关阅读:
    原生小程序音频播放
    Vue定义全局过滤器filter
    系统扩展性之引入外部包
    oracle update join
    OAuth2
    oracle pl/sql
    MySQL同步工具otter的使用介绍(一)
    python批量安装apk
    mac brew安装redis
    antd 修改Modal的底部按钮颜色
  • 原文地址:https://www.cnblogs.com/Yjianyong/p/2304541.html
Copyright © 2020-2023  润新知