C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

クラスのディープコピーについて

[トピック内 20 記事 (1 - 20 表示)]  << 0 >>

■92142 / inTopicNo.1)  クラスのディープコピーについて
  
□投稿者/ tro (17回)-(2019/08/29(Thu) 20:39:10)

分類:[C#] 

拡張メソッドを使用してクラスオブジェクトのディープコピーをしようとしていますが、
コピーを実行するとコピー先のクラスオブジェクトのイベント変数がnullになってしまいました。

イベント変数には[field:NonSerialized]の属性を付加しているためシリアライズされないという認識なのですが、
他に何か原因はあるのでしょうか?


・拡張メソッド
public static T DeepCopy<T>(this T src)
{
using (var objectStream = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(objectStream, src);
objectStream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(objectStream);
}
}

・イベント変数
[field:NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;

引用返信 編集キー/
■92143 / inTopicNo.2)  Re[1]: クラスのディープコピーについて
□投稿者/ Hongliang (879回)-(2019/08/29(Thu) 21:00:24)
> コピーを実行するとコピー先のクラスオブジェクトのイベント変数がnullになってしまいました。
>
> イベント変数には[field:NonSerialized]の属性を付加しているためシリアライズされないという認識なのですが、
> 他に何か原因はあるのでしょうか?

ご認識の通りですし、結果もその認識通りの結果になっているだけですが…?
引用返信 編集キー/
■92144 / inTopicNo.3)  Re[2]: クラスのディープコピーについて
□投稿者/ tro (18回)-(2019/08/30(Fri) 08:50:35)
2019/08/30(Fri) 08:51:37 編集(投稿者)

説明不足でもうしわけございません。
コピー先のイベント変数には値が設定されているのですが、
コピーを実行するとnullになってしまうということです。

シリアライズされないようにすると、
コピーは実施されなくても初期化はされてしまうということなのでしょうか。
引用返信 編集キー/
■92145 / inTopicNo.4)  Re[3]: クラスのディープコピーについて
□投稿者/ Hongliang (880回)-(2019/08/30(Fri) 09:08:11)
> コピー先のイベント変数には値が設定されているのですが、
> コピーを実行するとnullになってしまうということです。
>
> シリアライズされないようにすると、
> コピーは実施されなくても初期化はされてしまうということなのでしょうか。

ちょっとどういう想定なのか理解しかねます。
簡単なクラス定義とコピーを呼び出す側のコードとコピー前後の各変数の想定値を書いていただけますでしょうか。

// var dst = new { A = 3, B = 5 };
// var src = new { A = 1, B = null };
// dst = Copy(src);
// でdst.Bが5になっていない、みたいなこと…?
引用返信 編集キー/
■92146 / inTopicNo.5)  Re[4]: クラスのディープコピーについて
□投稿者/ tro (19回)-(2019/08/30(Fri) 09:17:23)
2019/08/30(Fri) 09:17:51 編集(投稿者)

クラスと呼び出し側のサンプルを記載しましたのでご教授願います。

・クラス定義
[Serializable]
public class TestClass
{
[field:NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;

public String Name{ get; set; }
}

・呼び出し
var dst = new TestClass();
src.PropertyChanged += TestClass_PropatyChanged;

var src = new TestClass();
src.Name = "Tarou";

dst = src.DeepCopy();
※ここでdstのPropertyChangedがnullになってしまいます。

引用返信 編集キー/
■92147 / inTopicNo.6)  Re[5]: クラスのディープコピーについて
□投稿者/ Hongliang (881回)-(2019/08/30(Fri) 09:26:40)
> var dst = new TestClass();
> src.PropertyChanged += TestClass_PropatyChanged;
>
> var src = new TestClass();
> src.Name = "Tarou";
>
> dst = src.DeepCopy();
> ※ここでdstのPropertyChangedがnullになってしまいます。

src.DeepCopy()した結果、新しいTestClassインスタンスが生成されて返され、それをdstに代入したわけです。
それまでdstに入っていたTestClassインスタンスはこの時点で失わますので、元々dstに何が入っていようが、新しいdstとは全く無関係です。

TestClass a = new TestClass() { Name = "A" };
a = new TestClass();
と記述した時にa.NameにAは入っていない、というのと状況は変わりません。
引用返信 編集キー/
■92148 / inTopicNo.7)  Re[3]: クラスのディープコピーについて
□投稿者/ 魔界の仮面弁士 (2332回)-(2019/08/30(Fri) 09:31:44)
No92144 (tro さん) に返信
> 説明不足でもうしわけございません。
> コピー先のイベント変数には値が設定されているのですが、
> コピーを実行するとnullになってしまうということです。

null というよりは、default になる感じ…?


[Serializable]
class Example
{
  public int A { get; set; }
  public int B;
  [field: NonSerialized] public int C;
  [field: NonSerialized] public int D = int.MaxValue;
}

private void button1_Click(object sender, EventArgs e)
{
  var src = new Example { A = 2, B = 4, C = 6 };
  var dst = new Example { A = 3, B = 5, C = 7 };

  // この時点では
  // src は { 2, 4, 6, 0x7fffffff }
  // dst は { 3, 5, 7, 0x7fffffff }

  dst = src.DeepCopy();

  // この時点では
  // src は { 2, 4, 6, 0x7fffffff }
  // dst は { 2, 4, 0, 0 }
}
引用返信 編集キー/
■92149 / inTopicNo.8)  Re[4]: クラスのディープコピーについて
□投稿者/ tro (20回)-(2019/08/30(Fri) 09:36:36)
2019/08/30(Fri) 09:37:09 編集(投稿者)

回答ありがとうござます。
[field:NonSerialized]の属性を付加しても代入の対象外とならないのでしょうか?

ちなみに以下のようにコピー先のイベント変数に値を格納してみましたが、
これでもコピー後にnullとなるようです。

・呼び出し
var dst = new TestClass();
dst.PropertyChanged += TestClassA_PropatyChanged;

var src = new TestClass();
dst.PropertyChanged += TestClassB_PropatyChanged;
src.Name = "Tarou";

dst = src.DeepCopy();
※ここでdstのPropertyChangedがnullになってしまいます。
引用返信 編集キー/
■92150 / inTopicNo.9)  Re[4]: クラスのディープコピーについて
□投稿者/ tro (21回)-(2019/08/30(Fri) 09:41:07)
No92148 (魔界の仮面弁士 さん) に返信
> ■No92144 (tro さん) に返信
>>説明不足でもうしわけございません。
>>コピー先のイベント変数には値が設定されているのですが、
>>コピーを実行するとnullになってしまうということです。
>
> null というよりは、default になる感じ…?

はい、確かに初期値になってしまっているように思えます。
[field:NonSerialized]属性はそういうものなのでしょうか?

引用返信 編集キー/
■92155 / inTopicNo.10)  Re[5]: クラスのディープコピーについて
□投稿者/ 魔界の仮面弁士 (2333回)-(2019/08/30(Fri) 11:28:00)
No92150 (tro さん) に返信
>>null というよりは、default になる感じ…?
> はい、確かに初期値になってしまっているように思えます。
> [field:NonSerialized]属性はそういうものなのでしょうか?

[Serialzable] なクラスをデシリアライズする際には、
そのクラスのコンストラクタが呼ばれません。

そのため No92148 でいうところの D = int.MaxValue の処理は実行されません。

ですから、シリアル化対象になっていないメンバーの値は、
初期値である default(T) のままであったということでしょう。


追加の初期値制御が必要な場合には、
 //[OnSerializing] private void OnSerializing(StreamingContext context) { }
 //[OnSerialized] private void OnSerialized(StreamingContext context) { }
 //[OnDeserializing] private void OnDeserializing(StreamingContext context) { }
 [OnDeserialized] private void OnDeserialized(StreamingContext context) { }
が使えます。あるいは昔ながらの IDeserializationCallback とか。

ただし、サポートしているシリアライザ(BinaryFormatter、SoapFormatter 等)と、
サポートしていないシリアライザ(XmlSerializer 等)があるので注意して下さい。

また、シリアライズ項目が動的に変化するようなケースでは、
Serializable 属性の代わりに
ISerializable インターフェイスを使って制御することができます。
引用返信 編集キー/
■92157 / inTopicNo.11)  Re[5]: クラスのディープコピーについて
□投稿者/ とっちゃん (623回)-(2019/08/30(Fri) 11:33:25)
No92150 (tro さん) に返信

dst = src.DeepCopy();

の = という処理を誤解しているように思います。

dst, src はともにclass(参照型)です。

参照型の代入演算子は、「左辺(dst)は、右辺(src)の参照先に変更する」です。

「参照先の変更」であって、「参照しているオブジェクトの内容を変更」ではありません。

DeepCopy と呼ばれる処理には以下の2パターンがあります。

1. 既存のオブジェクトに、内容を転送する DeepCopy
2. 既存のオブジェクトとは別の領域を用意して、内容を転送する DeepCopy

今回作りたいと思っているは1のDeepCopyだと思いますが、
提示されているものは2のDeepCopyです。

今回ほしいのは、内容を転送したい 1 の DeepCopy ですよね?

引用返信 編集キー/
■92159 / inTopicNo.12)  Re[5]: クラスのディープコピーについて
□投稿者/ 魔界の仮面弁士 (2334回)-(2019/08/30(Fri) 11:46:12)
No92149 (tro さん) に返信
> ちなみに以下のようにコピー先のイベント変数に値を格納してみましたが、
> これでもコピー後にnullとなるようです。

それ自体は、シリアル化の話とは無関係です。


> dst = src.DeepCopy();

上記の代入式において、代入直前まで変数 dst が参照していた内容は
一切考慮されることがありません。

左辺の変数が元々参照していたオブジェクトが何であれ、
元の参照情報は破棄され、その代わりに、
代入式の右辺(DeepCopy メソッドの戻り値)から得られる
新しいインスタンスへの参照がセットされる処理だからです。


もしも dst が持つ元の値を維持したいのであれば、
 dst = src.DeepCopy(); // あるいは「src.DeepCopy(out dst);」
の形式の呼び出しでは不可能です。

文法的な面だけで言えば、
 src.DeepCopy(dst);
もしくは
 src.DeepCopy(ref dst);
の形式が必要となるでしょう。


とはいえ、そもそも IFormatter.Deserialize() が「戻り値」として
結果を返す仕様なので、上記への書き換えが可能になるわけではないですが…。
引用返信 編集キー/
■92163 / inTopicNo.13)  Re[6]: クラスのディープコピーについて
□投稿者/ tro (22回)-(2019/08/30(Fri) 13:19:30)
No92157 (とっちゃん さん) に返信

> 1. 既存のオブジェクトに、内容を転送する DeepCopy
> 2. 既存のオブジェクトとは別の領域を用意して、内容を転送する DeepCopy
>
> 今回作りたいと思っているは1のDeepCopyだと思いますが、
> 提示されているものは2のDeepCopyです。
>
> 今回ほしいのは、内容を転送したい 1 の DeepCopy ですよね?
>

参照渡しではなく、(イベント変数を除いて)値型のようにコピーを行いたいのが目的です。
目的が果たせることができれば1でも2でも問題ありません。
引用返信 編集キー/
■92165 / inTopicNo.14)  Re[6]: クラスのディープコピーについて
□投稿者/ tro (23回)-(2019/08/30(Fri) 13:21:47)
No92159 (魔界の仮面弁士 さん) に返信

> 文法的な面だけで言えば、
>  src.DeepCopy(dst);
> もしくは
>  src.DeepCopy(ref dst);
> の形式が必要となるでしょう。
>
>
> とはいえ、そもそも IFormatter.Deserialize() が「戻り値」として
> 結果を返す仕様なので、上記への書き換えが可能になるわけではないですが…。

そうしますとコピーコンストラクタやCloneメソッドのようなものを実装しないと
目的が果たせないということでしょうか。
引用返信 編集キー/
■92167 / inTopicNo.15)  Re[7]: クラスのディープコピーについて
□投稿者/ 魔界の仮面弁士 (2337回)-(2019/08/30(Fri) 13:38:24)
2019/08/30(Fri) 13:40:53 編集(投稿者)

No92165 (tro さん) に返信
> そうしますとコピーコンストラクタやCloneメソッドのようなものを実装しないと
> 目的が果たせないということでしょうか。

Clone メソッドも、戻り値として結果を返すだけですから、
dst = src.Clone(); な形式では、dst の古い結果は上書きされますね。



dst が持つ NonSerialized フィールドの値を維持したいなら、
コピー時には src と dst の両方のインスタンスが必要となりますから、
呼び出す側のコードとしては、
 dst.FillBy(template: src);
あるいは
 dst2 = new TestClass(template1: src0, template2: dst1);
などの形式になるかと思います。

呼び出された側で、deep copy をどのように実装するのかは
別途考えねばなりませんが…。
引用返信 編集キー/
■92180 / inTopicNo.16)  Re[8]: クラスのディープコピーについて
□投稿者/ tro (24回)-(2019/08/30(Fri) 15:35:30)
No92167 (魔界の仮面弁士 さん) に返信
> 2019/08/30(Fri) 13:40:53 編集(投稿者)
>
> Clone メソッドも、戻り値として結果を返すだけですから、
> dst = src.Clone(); な形式では、dst の古い結果は上書きされますね。
>
> dst が持つ NonSerialized フィールドの値を維持したいなら、
> コピー時には src と dst の両方のインスタンスが必要となりますから、
> 呼び出す側のコードとしては、
>  dst.FillBy(template: src);
> あるいは
>  dst2 = new TestClass(template1: src0, template2: dst1);
> などの形式になるかと思います。
>
> 呼び出された側で、deep copy をどのように実装するのかは
> 別途考えねばなりませんが…。

回答ありがとうございます。
やはりシリアライズでコピーする方法ではなく、
コピーコンストラクタなどで地道にやるしかないみたいですね。

引用返信 編集キー/
■92184 / inTopicNo.17)  Re[7]: クラスのディープコピーについて
□投稿者/ tro (25回)-(2019/08/30(Fri) 16:09:43)
2019/08/30(Fri) 16:10:18 編集(投稿者)

勘違いしていました。
コピーコンストラクタを実装しても、
newしたインスタンスのデフォルト値がコピーコンストラクタの定義通りに変更されるだけで、
定義していない変数などはデフォルト値になるみたいですね。

var dst = new TestClass(src);

とすると対策としては、
代入演算子を実装して、
上書きしたいもの、したくないものを定義するのが良さそうでしょうか。

引用返信 編集キー/
■92199 / inTopicNo.18)  Re[8]: クラスのディープコピーについて
□投稿者/ Azulean (1083回)-(2019/08/30(Fri) 22:04:30)
No92184 (tro さん) に返信
> 代入演算子を実装して、
> 上書きしたいもの、したくないものを定義するのが良さそうでしょうか。

C# では代入演算子のオーバーライドはできませんので、そういった方法はできません。
なので、対象のクラスに CopyForm とか、「よそからコピーする」といった実装をするぐらいでしょう。
引用返信 編集キー/
■92203 / inTopicNo.19)  Re[9]: クラスのディープコピーについて
□投稿者/ tro (26回)-(2019/08/31(Sat) 22:00:39)
No92199 (Azulean さん) に返信
> ■No92184 (tro さん) に返信
>>代入演算子を実装して、
>>上書きしたいもの、したくないものを定義するのが良さそうでしょうか。
>
> C# では代入演算子のオーバーライドはできませんので、そういった方法はできません。
> なので、対象のクラスに CopyForm とか、「よそからコピーする」といった実装をするぐらいでしょう。

返信ありがとうございます。
確かに調べたところ代入演算子のオーバーライドはできないようですね。
コピーしたいものだけを代入するメソッドをクラスに定義する形になるでしょうか。

public void CopyFrom(TestClass src)
{
A = src.A;
B = src.B;
// C = src.C; // 代入対象外
D = src.D;
引用返信 編集キー/
■92204 / inTopicNo.20)  Re[10]: クラスのディープコピーについて
 
□投稿者/ Azulean (1084回)-(2019/09/01(Sun) 00:28:30)
No92203 (tro さん) に返信
> コピーしたいものだけを代入するメソッドをクラスに定義する形になるでしょうか。

そうするしかないでしょうね。
要求が「丸ごとコピー」ではないので、その自分の任意にコピーする部分を作りこむ必要があります。
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ