C#/UWPのDataGridに並べ替え／ソート機能を実装してみた

開発環境：VisualStudio2019 Community / Windows10


UWPでDataGridを使いたくて、
http://doinaka.blog.jp/archives/1077075304.html
を参考に

1.DataGridを追加
2.CSVを読み込んでモデルを生成
3.BindingしてDataGridに表示

まで実装した。


しかし、WinfowmのDataGridと違って、ヘッダークリックでソートされない。
調べてみると、ソーティング等は自前で実装しろとのこと。
https://docs.microsoft.com/en-us/windows/communitytoolkit/controls/datagrid_guidance/group_sort_filter#2-sorting

マジかよー。。。
仕方ないのでサンプルを見ながら実装しようとしたら、、、
ん？
//add code to handle sorting by other columns as required
ですって？
「ソートしたいカラムの数だけ同様のメソッドを実装して下さい」
と読み取れたんですがそれは、、、


今開発しているアプリのModelはプロパティメンバが20以上あって、仕様変更で増減する予定なんですけど。。。

たとえば、

```C#:ModelClass
class Person
{
    public string 姓 { get; set; }

    public string 名 { get; set; }

    public string 姓カナ { get; set; }

    public string 名カナ { get; set; }

    public string 郵便番号 { get; set; }

    public string 都道府県名 { get; set; }

    public string 市区町村名 { get; set; }

    public string 住所続き1（番地号） { get; set; }

    public string 住所続き2（マンション名など） { get; set; }

    public string 生年月日 { get; set; }

    public string 性別 { get; set; }

    public string 整理番号 { get; set; }

    public string 電話番号 { get; set; }

    public string 携帯電話 { get; set; }

    public string メールアドレス { get; set; }
}
```

こんなModelClassがあったとして、このModelをDataGridにBindingして内容表示をしたいとする。
ただし、仕様変更によって血液型や星座など、プロパティメンバは増減する可能性あり。

上記サイトのサンプルコードに忠実に従うならば、
1.クリックされたヘッダーに対応するプロパティを、Swich文を使ってプロパティメンバ全ての中から探し出してOrdeyBy
2.プロパティメンバが増えたり変更された場合は1.のコードも改修する
といったような実装になってしまう。
1だけでも冗長なコードで気持ち悪いのに、2に関しては完全にアウトですよね。


というわけで、上記の問題を解決するために少し考えた。
要はOrderBy()にわたすラムダ式（デリゲート）を動的に生成できればいいのだが、そんなことやったことないしどうやって実現するのかも検討がつかない。

"動的" "デリゲート" "ラムダ式"などでググりまくった結果、ExpressionTree（式ツリー）を使えばできそうだと言うことは分かった。
まずはExpressionTreeについての基礎知識にざっと目を通し、試しに実装（コーディング）しながら実践。

[参考にしたページ]
https://ufcpp.net/study/csharp/sp3_expression.html
https://qiita.com/elipmoc101/items/3d76457394815af98bb1

VisualStudioって偉大だよね。中途半端な知識で実装しても、文法エラーを具体的に指摘してくれるから実践しながら学習できる。

最終的に、簡潔なコードでModelClassのプロパティメンバの変更にも対応できる実装をすることができた。
コードは以下の通り。

```C#:UwpDatagrid_Sorting_Sample
//DataGridにバインドするModel
readonly ObservableCollection<Person> tmpPeople = new ObservableCollection<Person>();

private void datagrid_Sorting(object sender, DataGridColumnEventArgs e)
{
    List<Person> sortedPeople = new List<Person>();

    //p => p."columnName"というラムダ式を生成
    var keyFunc = GetDynamicPropertyExpression(e.Column.Header.ToString()).Compile();

    if (e.Column.SortDirection == null || e.Column.SortDirection == DataGridSortDirection.Descending)
    {
        //ColumnNameの中身でOrderBy
        sortedPeople = new List<Person>(tmpPeople.OrderBy(keyFunc));
        e.Column.SortDirection = DataGridSortDirection.Ascending;
    }
    else
    {
        //ColumnNameの中身でorderByDescending
        sortedPeople = new List<Person>(tmpPeople.OrderByDescending(keyFunc));
        e.Column.SortDirection = DataGridSortDirection.Descending;
    }

    //ソート結果をUIに反映
    ReloadDataGrid(sortedPeople);

    //他のカラムのソート状態をデフォルトにする
    foreach (var item in this.hihokenshaDatagrid.Columns)
    {
        if (item != e.Column) item.SortDirection = null;
    }
}

static Expression<Func<Person, string>> GetDynamicPropertyExpression(string columnName)
{
    var type = typeof(Person);
    var prop = type.GetProperty(columnName);
    var param = Expression.Parameter(type, "p");

    //p."columnName"というプロパティアクセス
    var propertyAccess = Expression.MakeMemberAccess(param, prop);

    //p => p."columnName"というラムダ式を格納した式ツリーを返す
    return Expression.Lambda<Func<Person, string>>(propertyAccess, param);
}