SortableBindingList その2

id:siokoshou:20070225 をもうちょっと改良してみるの巻。
CodeZineで同じネタがVB化されてました
元ネタはこちら。http://www.microsoft.com/japan/msdn/columns/winforms/winforms02182005.aspx
CodeZineの記事は明らかに元ネタを参考にしているのに参考資料にあげてないのは、元ネタのコードがそのままじゃ動かないから?それより、EULAに抵触するような気がしないでもないけど、動かないコードを載せたまま更新してないMSDNのほうがどうかと思うので、まあいいや。

この SortableBindingList、実はかなりお気に入り。SortableBindingList.DataSource に List BindingSource.DataSource に SortableBindingList( list ) を入れるだけでソートできるってのはホントに便利。リフレクションを使うから遅いんだけど、どうせ DataGridView の表示はその何百倍(?)も遅いので、まあいいかなと。

で、ふと、このコードの肝の PropertyComparer ってのは、SortBy のコアじゃないか!と気づいたわけです。List<エンティティクラス> list を、あるプロパティの順でソートしたいとき list.SortBy( プロパティ名 ); みたいに書ければ便利ですよね。C#3.0の拡張メソッドでこう書ける日がそのうち来るんだけど、でもまあ list.Sort( new PropertyComparer( property, direction ) ); でもいいかな。3段落続けて妥協ばかりw

プロパティ名のところは stringだと楽だけど、型安全にこだわる.NET的には PropertyComparer のように PropertyDescriptor か、PropertyInfo あたりかな。まあ、候補はこの3つ。

元ネタでは PropertyComparerhttp://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnadvnet/html/vbnet01272004.asp からのコピペってコメントがあるんだけど404。ぐぐってみると、あちこちに似たようなコードがありました。
トップに出てきた CodeProject のコードはなかなかよさげ。これは string でプロパティ名を指定してますね。
http://www.codeproject.com/useritems/PropertyComparer.asp


長い前振りはここまでにして、SortBy として使うとなるとやっぱり遅さが気になるんで、高速化にチャレンジしてみました。コードが気に入らないって理由もあったりしますが書いた人には内緒にしといてください。最初に断っておくと、まだ途中です。2倍弱速くなったけど、それだけ(^^;

Compare が呼ばれるたびにリフレクションで値を取り出すので、きっとここが遅いと。メモ化がおそらく効果的で、あとはリフレクションを使わないようにできないかなぁと妄想。できるのかどうか知らないけど、ジェネリクスとリフレクションのあたりはおもしろそうなので、いろいろ調べつつ挑戦。

前回載せなかった SortableBindingList も書いてしまいます。CodeZineが載せたんだからいいかな、なんて。

using System;
using System.Reflection;
using System.Collections.Generic;
using System.ComponentModel;

namespace SioKoshou
{
  public class SortableBindingList<T> : BindingList<T>
  {
    private PropertyDescriptor _sortProp = null;
    private ListSortDirection _sortDir = ListSortDirection.Ascending;
    private bool _isSorted = false;

    public SortableBindingList() { }
    public SortableBindingList( IList<T> list ) : base( list ) { }

    protected override void ApplySortCore( PropertyDescriptor property, ListSortDirection direction )
    {
      List<T> list = this.Items as List<T>;
      if ( list != null )
      {
        list.Sort( PropertyComparerFactory.Factory<T>( property, direction ) );

        this._isSorted = true;
        this._sortProp = property;
        this._sortDir = direction;

        this.OnListChanged( new ListChangedEventArgs( ListChangedType.Reset, -1 ) );
      }
    }

    protected override bool SupportsSortingCore { get { return true; } }
    protected override void RemoveSortCore() { }
    protected override bool IsSortedCore { get { return this._isSorted; } }
    protected override PropertyDescriptor SortPropertyCore { get { return this._sortProp; } }
    protected override ListSortDirection SortDirectionCore { get { return this._sortDir; } }
  }

  public static class PropertyComparerFactory
  {
    public static IComparer<T> Factory<T>( PropertyDescriptor property, ListSortDirection direction )
    {
      Type seed = typeof( PropertyComparer<,> );
      Type[] typeArgs = { typeof( T ), property.PropertyType };
      Type pcType = seed.MakeGenericType( typeArgs );

      IComparer<T> comparer = ( IComparer<T> ) Activator.CreateInstance( pcType, new object[] { property, direction } );
      return comparer;
    }
  }

  public sealed class PropertyComparer<T, U> : IComparer<T>
  {
    private PropertyDescriptor _property;
    private ListSortDirection _direction;
    private Comparer<U> _comparer;

    public PropertyComparer( PropertyDescriptor property, ListSortDirection direction )
    {
      this._property = property;
      this._direction = direction;
      this._comparer = Comparer<U>.Default;
    }

    public int Compare( T x, T y )
    {
      U xValue = ( U ) this._property.GetValue( x );
      U yValue = ( U ) this._property.GetValue( y );

      if ( this._direction == ListSortDirection.Ascending )
        return this._comparer.Compare( xValue, yValue );
      else
        return this._comparer.Compare( yValue, xValue );
    }
  }
}

RemoveSortCore の扱いは微妙…。オーバーライドしないほうがいいのかも?元ネタにある Save と Load は省略しました。

ジェネリック型のインスタンスの構築を参考に PropertyComparer の型パラメータ U にプロパティの型を動的に入れてみました。
あとは、U型のプロパティをリフレクションを使わないで取り出せれば、速くなりそうだなぁと思いつつ、そんなことができるのかも知らないので今日はここまで。ILを生成すればできないものかなぁ、ってあたりをそのうち調べてみます。あとメモ化も。(きっと)続く。

ちなみにこの時点で2倍弱速くなったのは、おそらく元コードの GetPropertyValue が冗長なため。比較のたびに PropertyInfo を取ってたとこを、PropertyDescriptor の GetValue に変えました。CodeZineの記事もここを真似ちゃってますね。