System.Data.DataRow は System.ComponentModel.IEditableObject をもたないだって?!
System.Data.DataRowはなぜIEditableObjectでないのか
しかし DataRow はIEditableObjectを(まったく同一のメソッドセットを公開しているにかかわらず)実装していない。
いやいや、それはないでしょう。BeginEdit も CancelEdit も EndEdit もある……
(まったく同一のメソッドセットを公開しているにかかわらず)実装していない。
……え? なにそれ?
ちょっと確かめる。
var data = typeof(DataRow)
.GetInterfaces()
.Select(a => a.ToString())
.DefaultIfEmpty("ないよ");
foreach (var datum in data)
{
Console.WriteLine(datum);
}
Console.ReadKey();
ないよ
ほんまや。
いやしかし、しかしですよ。DataRow と言えば IEditableObject を使用しているクラスの代表格だったはず。むしろ DataRow のために IEditableObject が作られたと言っても過言ではないはず。それがなぜ実装していないのか?
今までバインドしたコントロールでデータを変更して、キャンセルボタンで元に戻っていたのは何だったのか?
ん? 待てよ。そういえば……
var data = typeof(DataRowView)
.GetInterfaces()
.Select(a => a.ToString())
.DefaultIfEmpty("ないよ");
foreach (var datum in data)
{
Console.WriteLine(datum);
}
Console.ReadKey();
System.ComponentModel.ICustomTypeDescriptor
System.ComponentModel.IEditableObject
System.ComponentModel.IDataErrorInfo
System.ComponentModel.INotifyPropertyChanged
出た。
最初のコードと何が違うかというと、DataRow を DataRowView に変えたところです。これは何でしょうか?
DataTable はバインドされない
「そんなバカな」と思われた方もいらっしゃるかもしれません。Windows Forms アプリで、BindingSource の DataSource に DataTable を流し込んで DataGridView に表示させるのは定番中の定番です。しかし、この時に DataGridView に表示されていたのは、実は DataView という代理人だったのです。
IListSource
IListSource は「IList を実装したオブジェクトを返す」というよくわからないインターフェースです。
DataTable は IList を公開せず、代わりに IListSource を公開しています。これが何のためなのか、最初のうちは疑問でした。IListSource など実装しなくても、テーブルが追加・削除・変更・インデクシングできる集合であることは明らかなのだから、自分で IList を実装すればいいじゃんと思っていました。
この IListSource から得られるリストは本来 IList なのですが、DataTable の場合は IBindingListView を実装する DataView を返します。IBindingListView は IList の上位互換なので問題ありません。
IBindingListView
IBindingListView は要するに「ソート・フィルタリングの機能を持つリスト」のインターフェースです。これがあるからこそ、DataGridView で並び替えやフィルタリングか可能になるのです。
例えばソートはこういう流れになります。
1 BindingSource の DataSource に DataTable を入れる
2 DataTable は IList を実装せず IListSource を実装しているので、BindingSource は DataView を取得し、これを扱う
3 DataGridView から BindingSource にソート要求が来る
4 DataView は IBindingListView を実装しているので、BindingSource はソートを DataView に任せる
IBindingListView でなくても、IList もソートできるじゃん
例えば ArrayList も Sort() メソッドを持っていますから、ソートは可能です。しかし、要素の特定のプロパティに基づいてソートするためには、そのための比較関数を指定しなければなりません。
IBindingListView を実装したオブジェクトの場合は、「どのプロパティを昇順・降順で用いるか」を優先順位を指定して複数指定できますし、いつでもソート前の状態に戻すことができます。
なぜ DataTable は IList を公開しないのか
IBindingListView を実装したオブジェクトは、ソート・フィルタリング機能を提供します。この時、実際に要素を並び替えたり削除したりするのではパフォーマンスが悪くなります。そこで、現在のリストの要素を参照する別のリストを作り、現在のリストはいじらず仮のリスト(これをビューと呼びます)に対してソート・フィルタリングの操作を行う実装が簡単です。
DataTable はこの「現在のリスト」に相当し、DataView は「ビュー」に相当します。本来なら DataTable を隠して DataView だけ外から見えるようにしても良かったのですが、ビューを通してのアクセスは少しパフォーマンスが落ちるので、必要な時だけビューを作るという手法を選んだのでしょう。
DataTable を直接変更するのは、生のデータをいじるようなもので、DataView を破壊します。「ビューがある時はなるべく DataView を通してね」という意思表示が IList インターフェースの非公開となって表れているのではないかと推測します。
DataRowView とは
回り道をしました。
DataTable のビューが DataView だと書きました。DataView の行は DataRow ではなく、その代理の DataRowView です。これが IEditableObject を実装しています。
つまりは、実際にバインドされるのは DataRow ではなく DataRowView だから、IEditableObject は DataRowView が持っていれば問題ない。そして、DataRow の同名メソッドは主に DataRowView などのビューから使用されることを想定して公開されている。
と、こういうことなのではないでしょうか?
執筆日: 2017/03/30
Discussion