この記事はかなり実験的な内容となっております。
へぇ~そうなんだぁくらいに留めていただけると幸いです。
また、抽象的な表現が多く含まれます。苦手な方はそっ閉じしてくださいm(__)m
例えば、以下のような実装をするとします。
* DataGridView には dt (DataTable型) をバインドさせて表示する。
* dt を `Copy` して変更前のデータ dt_origin (DataTable型) を用意しておく。
* dt の変更が不適切であれば dt_origin を `Copy` して変更前に戻す。
↓ 念のため、バインドに関するドキュメントを載せておきます。
DataTable を変更するのであれば、実際には AcceptChanges
メソッドや RejectChanges
メソッドを使えばよいですが、DataTable どうしで Copy
し合う方法をとるとどうなるか、ふと気になったので調べてみました。
TL;DR
- データだけコピーしたいんだったら
for
文使った方が早いです。
実際にやってみた
以下のような DataTable (変更前: dt_origin, 変更する DataTable : dt) を作成し、DataGridView コントロール (命名: dgv) にバインドします。
id | name | explain |
---|---|---|
1 | タロウ | 長男 |
2 | ジロウ | 次男 |
3 | サブロウ | 三男 |
dgv.Columns.AddRange(new DataColumn[]
{
new DataColumn { ColumnName = "id", AllowDBNull = false, DataType = typeof(int) },
new DataColumn { ColumnName = "name", AllowDBNull = false, DataType = typeof(string) },
new DataColumn { ColumnName = "explain", AllowDBNull = true, DataType = typeof(string) }
});
dt.Rows.Add(new Object[] { 1, "タロウ", "長男" });
dt.Rows.Add(new Object[] { 2, "ジロウ", "次男" });
dt.Rows.Add(new Object[] { 3, "サブロウ", "三男" });
dgv.DataSource = dt; // dt をバインド
dt_origin = dt.Copy();
dt_origin には DataTable に組み込まれている Copy
メソッドを使って中身だけコピーしています。
DataTable は参照型であるため、
dt_origin = dt;
としてしまうと、dt と dt_origin は同一のオブジェクトとみなされることになるので注意が必要です。
なお、ここまでの段階で dt をバインドした dgv は以下のようになっています。
ここで、ジロウの id を 2 から 5 に変えてみます。
すると、dt の中身は以下のようになります。
一方、dt を Copy
した dt_origin は以下のように何も変化しません。
では、dt_origin を dt に Copy
することでこの変更をなかったことにしてみます。
dt = dt_origin.Copy();
dt は以下のようになります。
dt_origin はコピー元なので当然なにも変化しません。
ここまでは想定どおりです。
では、dgv はどうなっているでしょうか?
なんと、Copy
する前と同じく id は 5 のままです。
では、 ジロウの name を「ラーメン二郎」に変えてみましょう。id の変化も見てみたいので、「ラーメン二郎 (旧: ジロウ) 」の id は 5 のままでいきます。
この操作を経ると、 dt の中身は以下のようになりました。
何も操作してないので当然ですが、dt_origin の中身も変わっていません。
dgv の変更が dt に反映されていないことから、この時点ですでにバインドが解かれていることが予想出来ます。
こうなった原因を調べる上でなんとなく Copy
メソッドが怪しそうだと思い、調べてみたところ . . . . . . . . 答えが見つかりました。
DataTable の Copy
メソッドは「もう一人のぼく」を作り出す
公式ドキュメントによると、Copy
メソッドの定義と戻り値は以下のとおりです。
定義
この DataTable の構造体だけでなくデータもコピーします。戻り値
この DataTable と同じ構造体 (テーブル スキーマおよび制約) とデータを持つ新しい DataTable。
これらのクラスが派生されている場合は、コピーも同じ派生クラスになります。
Copy() は、新しい DataTable を元の DataTable と同じ構造とデータで作成します。 構造体を新しい DataTable にコピーし、データをコピーしない場合は、Clone() を使用します。
要は、ミイラ取りがミイラになる(?)ように、コピー先 DataTable がコピー元 DataTable になっちゃうというお話。
Copy
メソッドを使うと中身をコピーするのはもちろんのこと、自分がどんな構造体であるかという情報すらもコピーします。そうすることで、データを正しいインデックスに格納しているわけです。
(『コピーしたら型が違うカラムにデータが入っちゃった』みたいになることを防いでいます)
. . . ちょっとまって。それじゃあ直接代入する場合と同じじゃない?と思うかもしれませんが、違います。
それぞれの働きは以下の通りです。
✅ Copy
メソッドは DataTable の ディープコピー (実体を複製する。一方の変更は他方に反映されない) を行います。
✅ 直接代入するのは シャローコピー (実体を複製しない。コピー元とコピー先は同一のもの) です。
もっとかみ砕いて説明します。
Copy
メソッドを使うと、自分と同じ「もう一人のぼく」を生み出します。人間的にいえば、「もう一人のぼく」「もう一人のぼく」は自分と同じなので容姿や性格 (DataTable でいうカラム名やデータ) は全く同じですが、当然自分自身ではないので 「もう一人のぼく」がいま何を考えているかまではわかりません 。自分自身と「もう一人のぼく」は同じ人物であるかのように見えますが、存在自体はまったく別人なわけです。
(そういう意味ではクローン技術に近いですね。DataTable にはデータをコピーしない Clone
メソッドもあるみたいですが、その名前はここからきてるんでしょうか)
一方、直接代入すると 自分そのものをもう一つ生み出す ことになります。Copy
メソッドではできなかった記憶の共有が可能になるわけです (データ変更の反映)。誰もが一度は思う『自分がもう一人いればなぁ』を実現するのが直接代入なのです。
Copy
すると DataGridView とコピー先 DataTable のバインドは解ける
上記のことが理解できれば、この見出しのナゾも理解できると思います。
Copy
するとコピー先 DataTable はコピー元 DataTable にとっての「もう一人のぼく」になるので、コピー先 DataTable がバインドされている DataGridView からすると、コピー先 DataTable はまったくの別人になってしまうわけです。誰をバインドしていたのか分からなくなるわけですから、DataTable の変更が DataGridView に反映されないのは当然ですね。
結論:データだけコピーしたいんだったらおとなしく for
文使いましょう。
その方が無難です。
あとがき
Copy
メソッドの振る舞いについて、本質的な部分をざっくり理解できるように書いたつもりですが、わからない部分や間違い箇所等あればコメント欄でご指摘いただけると幸いです。