System.Data.DataRow は System.ComponentModel.IEditableObject をもたないだって?!

2021/09/02に公開

System.Data.DataRowはなぜIEditableObjectでないのか

しかし DataRow はIEditableObjectを(まったく同一のメソッドセットを公開しているにかかわらず)実装していない。

いやいや、それはないでしょう。BeginEditCancelEditEndEdit もある……

(まったく同一のメソッドセットを公開しているにかかわらず)実装していない。

……え? なにそれ?
ちょっと確かめる。

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

出た。

最初のコードと何が違うかというと、DataRowDataRowView に変えたところです。これは何でしょうか?

DataTable はバインドされない

「そんなバカな」と思われた方もいらっしゃるかもしれません。Windows Forms アプリで、BindingSourceDataSourceDataTable を流し込んで DataGridView に表示させるのは定番中の定番です。しかし、この時に DataGridView に表示されていたのは、実は DataView という代理人だったのです。

IListSource

IListSource は「IList を実装したオブジェクトを返す」というよくわからないインターフェースです。

DataTableIList を公開せず、代わりに IListSource を公開しています。これが何のためなのか、最初のうちは疑問でした。IListSource など実装しなくても、テーブルが追加・削除・変更・インデクシングできる集合であることは明らかなのだから、自分で IList を実装すればいいじゃんと思っていました。

この IListSource から得られるリストは本来 IList なのですが、DataTable の場合は IBindingListView を実装する DataView を返します。IBindingListViewIList の上位互換なので問題ありません。

IBindingListView

IBindingListView は要するに「ソート・フィルタリングの機能を持つリスト」のインターフェースです。これがあるからこそ、DataGridView で並び替えやフィルタリングか可能になるのです。

例えばソートはこういう流れになります。

1 BindingSourceDataSourceDataTable を入れる
2 DataTableIList を実装せず IListSource を実装しているので、BindingSourceDataView を取得し、これを扱う
3 DataGridView から BindingSource にソート要求が来る
4 DataViewIBindingListView を実装しているので、BindingSource はソートを DataView に任せる

IBindingListView でなくても、IList もソートできるじゃん

例えば ArrayListSort() メソッドを持っていますから、ソートは可能です。しかし、要素の特定のプロパティに基づいてソートするためには、そのための比較関数を指定しなければなりません。

IBindingListView を実装したオブジェクトの場合は、「どのプロパティを昇順・降順で用いるか」を優先順位を指定して複数指定できますし、いつでもソート前の状態に戻すことができます。

なぜ DataTableIList を公開しないのか

IBindingListView を実装したオブジェクトは、ソート・フィルタリング機能を提供します。この時、実際に要素を並び替えたり削除したりするのではパフォーマンスが悪くなります。そこで、現在のリストの要素を参照する別のリストを作り、現在のリストはいじらず仮のリスト(これをビューと呼びます)に対してソート・フィルタリングの操作を行う実装が簡単です。

DataTable はこの「現在のリスト」に相当し、DataView は「ビュー」に相当します。本来なら DataTable を隠して DataView だけ外から見えるようにしても良かったのですが、ビューを通してのアクセスは少しパフォーマンスが落ちるので、必要な時だけビューを作るという手法を選んだのでしょう。

DataTable を直接変更するのは、生のデータをいじるようなもので、DataView を破壊します。「ビューがある時はなるべく DataView を通してね」という意思表示が IList インターフェースの非公開となって表れているのではないかと推測します。

DataRowView とは

回り道をしました。

DataTable のビューが DataView だと書きました。DataView の行は DataRow ではなく、その代理の DataRowView です。これが IEditableObject を実装しています。

つまりは、実際にバインドされるのは DataRow ではなく DataRowView だから、IEditableObjectDataRowView が持っていれば問題ない。そして、DataRow の同名メソッドは主に DataRowView などのビューから使用されることを想定して公開されている。
と、こういうことなのではないでしょうか?

執筆日: 2017/03/30

GitHubで編集を提案

Discussion