LoginSignup
0
0
この記事誰得? 私しか得しないニッチな技術で記事投稿!

【C#・.NET Framework・WinForms】DataGridView にバインドしている DataTable に別の DataTable を Copy することでわかったこと

Posted at

この記事はかなり実験的な内容となっております。
へぇ~そうなんだぁくらいに留めていただけると幸いです。

また、抽象的な表現が多く含まれます。苦手な方はそっ閉じしてください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 サブロウ 三男
C#
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 は参照型であるため、

C#
dt_origin = dt;

としてしまうと、dt と dt_origin は同一のオブジェクトとみなされることになるので注意が必要です。

なお、ここまでの段階で dt をバインドした dgv は以下のようになっています。
first view of dgv
ここで、ジロウの id を 2 から 5 に変えてみます。
image.png
すると、dt の中身は以下のようになります。
contents of dt which was changed
一方、dt を Copy した dt_origin は以下のように何も変化しません。
image.png

では、dt_origin を dt に Copy することでこの変更をなかったことにしてみます。

C#
dt = dt_origin.Copy();

dt は以下のようになります。
image.png
dt_origin はコピー元なので当然なにも変化しません。
image.png
ここまでは想定どおりです。
では、dgv はどうなっているでしょうか?
image.png

なんと、Copy する前と同じく id は 5 のままです。
では、 ジロウの name を「ラーメン二郎」に変えてみましょう。id の変化も見てみたいので、「ラーメン二郎 (旧: ジロウ) 」の id は 5 のままでいきます。
image.png
この操作を経ると、 dt の中身は以下のようになりました。
image.png
何も操作してないので当然ですが、dt_origin の中身も変わっていません。
image.png

dgv の変更が dt に反映されていないことから、この時点ですでにバインドが解かれていることが予想出来ます。
こうなった原因を調べる上でなんとなく Copy メソッドが怪しそうだと思い、調べてみたところ . . . . . . . . 答えが見つかりました。

DataTable の Copy メソッドは「もう一人のぼく」を作り出す

公式ドキュメントによると、Copy メソッドの定義と戻り値は以下のとおりです。

定義
この DataTable の構造体だけでなくデータもコピーします。

戻り値
この DataTable と同じ構造体 (テーブル スキーマおよび制約) とデータを持つ新しい DataTable。
これらのクラスが派生されている場合は、コピーも同じ派生クラスになります。
Copy() は、新しい DataTable を元の DataTable と同じ構造とデータで作成します。 構造体を新しい DataTable にコピーし、データをコピーしない場合は、Clone() を使用します。

要は、ミイラ取りがミイラになる(?)ように、コピー先 DataTable がコピー元 DataTable になっちゃうというお話。
Many go out for wool and come home shorn

Copy メソッドを使うと中身をコピーするのはもちろんのこと、自分がどんな構造体であるかという情報すらもコピーします。そうすることで、データを正しいインデックスに格納しているわけです。
(『コピーしたら型が違うカラムにデータが入っちゃった』みたいになることを防いでいます)

. . . ちょっとまって。それじゃあ直接代入する場合と同じじゃない?と思うかもしれませんが、違います。
それぞれの働きは以下の通りです。

Copy メソッドは DataTable の ディープコピー (実体を複製する。一方の変更は他方に反映されない) を行います。
✅ 直接代入するのは シャローコピー (実体を複製しない。コピー元とコピー先は同一のもの) です。

もっとかみ砕いて説明します。

Copy メソッドを使うと、自分と同じ「もう一人のぼく」を生み出します。人間的にいえば、「もう一人のぼく」「もう一人のぼく」は自分と同じなので容姿や性格 (DataTable でいうカラム名やデータ) は全く同じですが、当然自分自身ではないので 「もう一人のぼく」がいま何を考えているかまではわかりません 。自分自身と「もう一人のぼく」は同じ人物であるかのように見えますが、存在自体はまったく別人なわけです。

(そういう意味ではクローン技術に近いですね。DataTable にはデータをコピーしない Clone メソッドもあるみたいですが、その名前はここからきてるんでしょうか)
cloned men

一方、直接代入すると 自分そのものをもう一つ生み出す ことになります。Copy メソッドではできなかった記憶の共有が可能になるわけです (データ変更の反映)。誰もが一度は思う『自分がもう一人いればなぁ』を実現するのが直接代入なのです。

Copy すると DataGridView とコピー先 DataTable のバインドは解ける

上記のことが理解できれば、この見出しのナゾも理解できると思います。

Copy するとコピー先 DataTable はコピー元 DataTable にとっての「もう一人のぼく」になるので、コピー先 DataTable がバインドされている DataGridView からすると、コピー先 DataTable はまったくの別人になってしまうわけです。誰をバインドしていたのか分からなくなるわけですから、DataTable の変更が DataGridView に反映されないのは当然ですね。

結論:データだけコピーしたいんだったらおとなしく for 文使いましょう。

その方が無難です。

あとがき

Copy メソッドの振る舞いについて、本質的な部分をざっくり理解できるように書いたつもりですが、わからない部分や間違い箇所等あればコメント欄でご指摘いただけると幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0