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