回答済み DataRow の持つ値が不意に変化する

  • 2012年5月2日 8:12
     
      コードあり

    DataRow の扱い方によって、値が不意に変わるという気になる挙動を見つけました。
    .NET Framework のバグのように思えますが、調べても情報を見つけられなかったので、Connect に投稿する前にご意見や情報をいただければと思います。
    簡単な手順で発生するので既出かもしれません。

    試した環境は次のとおりで、どれでも再現します。
    ・Windows7 x64 Professional / VS2010 Professional / .NET 3.5 / C#
    ・Windows7 x64 Professional / VS2010 Professional / .NET 4.0 / C#
    ・Windows7 x64 Professional / VS11 Professional Beta / .NET 4.5 Beta / C#
    ・Windows7 x64 Professional / VS11 Professional Beta / .NET 4.5 Beta / VB

    C# での再現コードです。

    DataTable table = new DataTable();
    table.Columns.Add("Id", typeof(int));
    table.Rows.Add(0);
    table.Rows.Add(1);
    table.Rows.Add(2);
    table.AcceptChanges();
    
    // (1) Id = 1 の行を削除
    table.Rows.Remove(table.Select("Id = 1")[0]);
    
    // (2) 新しい行を作成。AddRow はしない。
    DataRow row = table.NewRow();
    row["Id"] = 777;
    
    // (3) データテーブルをクリアし、データをバックアップから復元
    //     ※ 変化1 ※ row["Id"] の値が DBNull に変化
    table.Clear();
    
    // (4) データを再度追加
    table.Rows.Add(3);
    table.Rows.Add(4); // (5) Id = 4 を追加  ※変化2※ row["Id"] の値が 4 に変化
    table.Rows.Add(5);
    
    // (6) row["Id"] の値を設定  ※変化3※ table.Rows 配下の Id = 4 の行の値が 999 に変化
    row["Id"] = 999;
    

    ポイントとなる処理のコメントに番号をつけています。値が不意に変わる処理は (3)、(5)、(6) です。
    AcceptChanges() をした DataTable や、TableAdapter で Fill(~) した DataTable に対して
    1. (1) の 行削除
    2. (2) の NewRow()
    3. (3) の Clear()
    の順に処理すると発生します。

    なお、(1) は table.Select("Id = 1")[0].Delete(); table.AcceptChanges(); としても発生しますし、
    (4) は table.Merge(~); や table.ImportRow(~); としても発生します。

    ・上記コードで再現できた、または再現できない
    ・この挙動は仕様である
    など、ご意見や情報がありましたらお願いします。

    また、上記の順に処理を実装しないことが現状の回避策になるかと思いますが、他に回避策があれば記述をお願いします。
    (そもそも NewRow() から Rows.Add(~) の間に Clear() のような DataTable の行を操作するメソッドを呼び出すべきではないと思っています。)

すべての返信

  • 2012年5月2日 9:17
     
     回答済み

    DataTable.Clear メソッド の(.Net3.0以降の)説明には

    NewRowを使用して新しい行を作成する場合、Clear を呼び出す前に行を処理する必要があります。

    と書かれています。つまり、仕様ですね。

    ClearでTableに属しているDataRowはすべて無効になるので、Clear以降に以前のDataRowを扱うことは間違いです。
    NewRowはAddされるまでTableに属していないように見えるかもしれませんが、実際にはTable内部で管理されています。
    DataRowのコンストラクタがprotectedされているのも、Tableに所属していないDataRowを作らせないためです。

    #RowStateのDetachedが「追加される前」と「削除後」で同じというのが面倒くさい・・・

    前のDataRowに値を入れると、後のDataRowの値が変わってしまうのは、Table内部のコレクションでDataRowの入れ物を使いまわしてる可能性はあります。
    たぶんポインタみたいなものなのだと思います。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク hpyamada 2012年5月2日 9:54
    •  
  • 2012年5月2日 9:53
     
     

    返信いただきありがとうございます。

    リンク先を確認しました。なるほど、DataTable に追加されている/追加されていないに関わらず「Clear() する前に作成した DataRow を使用してはならない。」ということですね。
    DataRow を使いまわしている可能性は考えましたが、実体を比較しても false でしたので除外していました。DataRow と DataTable はもっと密な関係なのですね。

    私の調査不足です。このようなことでお時間を割いてくださり、本当にありがとうございました。
    おかげさまで、確かな回避策を取ることができます。