DOBON.NET DOBON.NETプログラミング掲示板過去ログ

Hashtableが値参照でコピーできない?

環境/言語:[C#]
分類:[ASP.NET]

困っているわけではなく、納得できないので、どなたかご存知の方がおられたら、おしえてください。

ArrayListの中にHashtableを入れて、Hashtableのキー値にフィールド名をセットしてレコードとして扱い、一覧表を生成するアプリを作っていました。
で、行追加する時に、適当な1行を、ArrayList内から取り出して、そのHashtable内の全データをクリアして、新規にAddしてました。
すると、その取り出した適当な1行がクリアされちゃうんですね。

あ。これは値参照じゃなくてアドレス参照なんだな。と思って。

Hashtable ht = new Hashtable(row);

とかやって、Hashtableをコピーしたつもりになってたんですが…。
htを編集しても、rowも同時に更新されるのです。
結局、rowの全要素をループして、htに新規の値を設定するという、かなり泥臭い手法で逃げたのですが…。納得できない。

Hashtable ht = row;

がアドレス参照になるのはわかるんですが、どうして

Hashtable ht = new Hashtable(row);

これまでアドレス参照になるんでしょう?
というか、これで問題になってないケースもあるので(上記記述をした後、双方のハッシュをセッションに登録し、再呼び出しして使ったら、ちゃんと値参照になっててコピーされてた)、余計に不思議です。

聞きたい事としては、

1.Hashtableって、どこまでもアドレス参照なの?
2.Hashtableをコピーする方法ってあるの?

 の2点です。
こんばんは。

Hashtable ht = row;
は、コレクションリストそのものも同一オブジェクトとするもので、
Hashtable ht = new Hashtable(row);
は、
ふたつの独立したコレクションリストがそれぞれ同じ要素たちを参照している状態を作るものです。
スタート時に同じ状態の、独立したリストを作成するために使うもの。
元の状態と変化した状態を簡単に突き合わせたりできるようにする際に元データをベースに初期状態を
作成するのに使用できます。

ROWだけがる状態では
ROW
ABCDEFGHI   (これはキーだと思ってください)
↓↓↓↓↓↓↓↓↓
●●●●●●●●●   (何か参照先たちだと思ってください。ばらばらの。)
だったのが、
「Hashtable ht = new Hashtable(row);」を実行すると
ROW
ABCDEFGHI
↓↓↓↓↓↓↓↓↓
●●●●●●●●●
↑↑↑↑↑↑↑↑↑
ABCDEFGHI
HT
になる、ということです。
お互い関連のないリストが、たまたま全要素同じものを参照している状態です
この後、
HT.Add(何か,何か)
としたり
HT.RemoveAt(何か)
等をしたりすればお互いのリストはどんどん乖離していきます。
たまたま同じものを参照している要素があれば変更はどちらからも見えます。
キーFで参照している先の●のプロパティ「●.Text」に「あいうえお」をセットすれば、
この段階ではROW[F]もHT[F]も同じものを見ているのであいうえおに変わったのが見えます。

ROW
ABCDEFGHI
↓↓↓↓↓↓↓↓↓
●●●●●●●●●
↑ ↑↑↑ ↑↑↑
A CDEFGHIZ→●
HT    ↓
     ●
※この図は、HTからはBキーの要素は削除され(※単にHashtableからの要素の削除。参照先は消えません。)、
お互いのリストのキー「F」は別々のものを指し、
HTで、ROWにはないZキーの要素が何か参照している、変化が進んだ状態です。
この段階で、ROWの方のキーFで参照している先の●のプロパティ「●.Text」に「あいうえお」をセットしても、
HT[F]は別の実体を覗き見ているので変化しません。


で、
「Hashtable ht = row;」
は、
ROW  =  HT
ABCDEFGHI
↓↓↓↓↓↓↓↓↓
●●●●●●●●●
なので、ふたつのリストは何をどうやっても乖離しません。
(ROW自体に何か代入したりHT自体に何か代入した際だけ、お互い無関係になります。)
■No26414に返信(あきよしさんの記事)
質問の意図がわかりませんが、「一覧表」なら DataTable で管理した方が楽なのでは?


> 困っているわけではなく、納得できないので、

何を行ったのか/何に納得できないのか、元のコードを連想しきれませんでした…。

----------------------
 // ArrayListの中にHashtableを入れて、
 // Hashtableのキー値にフィールド名をセットして
 // レコードとして扱い、一覧表を生成する
 // アプリを作っていました。
 ArrayList z = new ArrayList();
 Hashtable a;
 a = new Hashtable();
 a.Add("aa", 123);
 a.Add("bb", 456);
 z.Add(a);
 a = new Hashtable();
 a.Add("aa", 999);
 a.Add("bb", 888);
 z.Add(a);
 /*
  この時点で、ArrayList の中はこのような状態になる。
    [0] :  aa=123, bb=456
    [1] :  aa=999, bb=888
 */


 // 行追加する時に、適当な1行を、
 // ArrayList内から取り出して、
 Hashtable hash = (Hashtable)z[0];
 /*
  取り出された Hashtable は「aa=123, bb=456」。
  この時点では Hashtable への参照を得ただけなので、
  ArrayList の状態は変化しない。
 */


 // そのHashtable内の全データをクリアして、
 hash.Clear();
 /*
  これにより、ArrayList の内容は
    [0] :  空っぽの Hashtable
    [1] :  aa=999, bb=888
  となる。
 */


 //新規にAddしてました。
 hash.Add("aa", 110);
 hash.Add("bb", 119);
 /*
  これにより、ArrayList の内容は
    [0] :  aa=110, bb=119
    [1] :  aa=999, bb=888
  となる。
 */


 // その取り出した適当な1行がクリアされちゃうんですね。

この部分の意味がわかりませんでした。
具体的にはどのようなコードを実行していて、それが
どのような結果になることを期待していたのでしょうか?

----------------------


> あ。これは値参照じゃなくてアドレス参照なんだな。と思って。
Hashtable はクラス、すなわち参照型なので、そうなるでしょうね。

……それとも、Hashtable そのものの話ではなく、
Hashtable 内に格納したオブジェクトの話をされていますか?
だとしたら、その Hashtable 内に登録したオブジェクトは
値型ですか? それとも参照型ですか?



> 聞きたい事としては、
> 1.Hashtableって、どこまでもアドレス参照なの?
ここでいう「アドレス参照」という言葉の意味にもよりますが、
どのようなオブジェクトを登録させるかによるかと思います。

少なくとも、
 hash.Add("hash", hash);
などのようにして、自分自身を登録した場合には、
 ((Hashtable)((Hashtable)hash["hash"])["hash"])["hash"]
などのように、どこまでも自身への参照が続くことになるでしょう。



> 2.Hashtableをコピーする方法ってあるの?
Hashtable の何をコピーするのでしょうか?

Hashtable に格納されていたのが、int 等の「値型」の場合は良いとして、
「参照型」たとえば "C:\a.txt" への FileStream が格納されていた場合、
結果として、どのようにコピーされることを期待しますか?


(1) "C:\a.txt" を開いてはいるが、元の FileStream とは
 異なるインスタンスのオブジェクトを格納している状態。
 → 排他オープンされていた場合はエラーになりますが、それで良いですか?

(2) 元の FileStream への参照情報をコピーした状態。
 → 同一インスタンスです。これを期待しているわけではないのですよね?

(3) "C:\a.txt" ではなく "C:\aのコピー.txt" を開いた FileStream が
 格納されている状態。
 → 別ファイルの名前はどのようにして決定したいのでしょうか?
こどさん、魔界の仮面弁士さん、レスありがとうございます。

なるほど。自分の勘違いがわかりました。
うまく説明できてるか自信はないのですが、要するにHashtableというオブジェクト
について、誤解があったのです(まあ、誤解があるだろう事が想像できたので、質
問させてもらったのですが)

私の勘違いは、

「Hashtableが部分的にアドレス参照でなければおかしい」

という思い込みがあったのだと想います。
Hashtableに格納したものは実際の値が存在し、Hashtableから取り出したものは、
Hashtable内にある値をアドレスで参照していると思っていました。

Hashtable内に入れていたものは、string配列でアドレス値です。
あくまでHashtableがアドレスしか格納されていないのであれば、納得できます。
言われてみれば値をもHashtable内に格納するのは、技術的に難しそうだし、処理
が重くなりそうで、意味がなさそうな気がする…。
解決済み!
■No26422に返信(あきよしさんの記事)
> 「Hashtableが部分的にアドレス参照でなければおかしい」
> という思い込みがあったのだと想います。
部分的なアドレス参照、という言葉のイメージが読み取れませんでしたが、恐らくは、
 値型
 参照型
 ボックス化(boxing)
 ボックス化解除(unboxing)
について学んでおくと、理解が進むであろうかと思います。


> Hashtable内に入れていたものは、string配列でアドレス値です。
配列の中身が 値型であれ参照型であれ、配列そのものは「参照型」ですね。


> 言われてみれば値をもHashtable内に格納するのは、技術的に難しそうだし、
この場合の『値』という言葉の意味が曖昧ですが、Hashtable 自体は
値型(数値や構造体など)を格納する事もできますよ。

static void Main()
{
    Hashtable hash = new Hashtable();
    hash.Add("Ref", new RefObj(111));  // 参照型を登録
    hash.Add("Val", new ValObj(222));  // 値型を登録

    // 変更前
    Console.WriteLine(hash["Ref"]);     // 111
    Console.WriteLine(hash["Val"]);     // 222

    RefObj r = (RefObj)hash["Ref"];
    ValObj v = (ValObj)hash["Val"];
    r.x = 10;
    v.x = 90;

    // 変更後
    Console.WriteLine(hash["Ref"]);     // 10
    Console.WriteLine(hash["Val"]);     // 222 (90ではない)
}


class RefObj
{
    public int x;
    public RefObj(int x) { this.x = x; }
    public override string ToString() { return x.ToString(); }
}
struct ValObj
{
    public int x;
    public ValObj(int x) { this.x = x; }
    public override string ToString() { return x.ToString(); }
}
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板