🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

1回答

1243閲覧

構造体のDefensive copyとボックス化について(readonly struct / in引数 /ジェネリック)

ry188472

総合スコア74

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2021/01/29 08:14

編集2021/02/01 00:33

特定のデータを受け取って処理をするジェネリッククラス/メソッドを作りたかったので、以下宣言をしました。

c#

1// 受け取れるデータのインターフェイス(値取得しかしないメソッドを持つ) 2public interface IMark { 3 bool IsMarkXyz(); 4} 5 6// IMarkを受け取ってなんやかんやする処理クラス 7// 引数の内容の書き換えは絶対にしない(例えばListを持っていても追加したりもしない) 8public class Executer { 9 public bool Execute1<T>(T data) where T : IMark { /* 処理 */ } 10 public bool Execute2(IMark data) { /* 処理 */ } 11 public bool Execute3<T>(in T data) where T : IMark { /* 処理 */ } 12 public bool Execute4<T>(IEnumerable<T> data) where T : IMark { /* 処理 */ } 13}

そのデータはイミュータブルでよく、サイズが十分小さそうだったので、以下宣言をしました。

c#

1public readonly struct HogeData : IMark { 2 // データの例 3 public int Id { get; } 4 public string Text { get; } 5 public HogeData(int id, string text) { Id = id; Text = text; } 6 7 public bool IsMarkXyz() { /* IMarkに既定された、値を取得する処理 */ } 8}

ここで、readonly struct と in引数の理解がちゃんとできているか自信がないです。
特に、inで受け取らないメソッドにreadonly structの変数を渡したときの動作がよくわかりません。
メモリ管理、Defensive copy、ボックス化について以下の内容であっているか確認させてください。
?がついているのは自信がない部分です。

c#

1 HogeData hoge = new HogeData(0, "Xyz"); // 構造体は値型なのでスタックにある。 2 hoge.IsMarkXyz(); // -> readonly structなのでDefensive copyは起きない。 3 4 IMark hoge2 = new HogeData(1, "abc"); // Box化が起きてヒープに行く。 5 hoge2.IsMarkXyz(); // -> ヒープ化は起きているが中身はreadonly structなのでDefensive copyは起きない? 6 7 var exe = new Executer(); 8 exe.Execute1<HogeData>(hoge); // -> Execute1はinで受け取らないので、コピーが起きる??? 9 exe.Execute2(hoge); // -> ボックス化が起きる。参照型になっているためコピーされない。 10 exe.Execute3<HogeData>(in hoge); // -> inで受け取るメソッドに渡しているのでコピーは起きない。 11 exe.Execute4<HogeData>(new[] { hoge }); // -> 配列は参照型なのでヒープに行き、コピーは起きない?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

こんにちは。

構造体の Defensive copy とは、「書き換え不能領域に置かれた構造体のメソッドを呼ぶ際に、そのメソッドが自身の値を書き換えない保証がないため予めスタック領域にコピーしてから実行する」というものです。
構造体が書き換え不能領域に置かれるのは、readonly field に配置した場合や in parameter で受け取った場合などです。
このとき対象の構造体が readonly でマークされていれば、そのシナリオにおける Defensive copy が抑制されます。
すなわち、「readonly struct を扱う場合は Defensive copy は一切発生しない」ということです。
readonly struct とは、「構造体のメソッドが自身の値を書き換えないという保証」そのものです。
また、値が書き換え可能領域に配置されている場合はそもそも Defensive copy は行われないため無関係です。

投稿2021/02/01 07:57

tamoto

総合スコア4252

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ry188472

2021/02/02 01:03

なるほど。ということは、 exe.Execute1<HogeData>(hoge); exe.Execute3<HogeData>(in hoge); はHogeDataがreadonly structなので実質的に等価(readonlyじゃなければinは有効)ということですね?
tamoto

2021/02/02 01:59

どちらも Defensive copy はなされませんが、等価と言ってしまうと誤解を招くかと。 Execute1 は値そのものを引数に取っていて、Execute3 は値の格納された場所へのアドレスを引数に取っています。 ただし引数が readonly struct であるならこれらの違いがコードの挙動に影響することはありません。
ry188472

2021/02/02 07:38

そもそもこんなことするなよ!という質問で申し訳ないのですが・・・ 例えばreadonly structをクラスの書き換え可能なメンバ変数(readonlyでないフィールド/set可能なプロパティ)を持っていて、そのメンバ変数をメソッドの引数に渡した場合を考えます。 Execute3 の in の場合は参照渡しなので、Execute3の処理の中で引数に渡したメンバ変数をnewし直すと、参照先が変わるので内容の変更が反映されてしまいますよね。 Execute1 は値渡しなので、同じことをしてもメソッド呼び出し時点の引数の内容が保証されているんでしょうか?
tamoto

2021/02/02 08:08

Execute3 は in parameter なので何を渡していても new し直した値を代入することはできません。 in parameter で渡した領域は、メソッドからは一時的に書き換え不能領域として認識されているということです。 試してみれば良いのではないでしょうか。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問