質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.48%
C#

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

Q&A

6回答

800閲覧

[C#]構造体メンバへの代入時のCS1612エラー原因を教えてください。

KKKM

総合スコア16

C#

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

1グッド

0クリップ

投稿2024/02/15 05:22

構造体メンバへの代入を行おうとしたところ、CS1612エラーが発生しました。
下記サイトを読んだのですが、いまいちはっきり理解ができず、フォローをいただきたいです。
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/compiler-messages/cs1612
一旦私の解釈を書きます。

C#

12List<MyStruct> list = {}; 3list[0].Name = "MyStruct42"; //CS1612

まず↑でCS1612エラーとなる理由
list[0].Name部分はlist[0].NameのGetterから取得してきた値そのものであるため
何らかの値をいれることはできない。

C#

12List<MyStruct> list = {}; 3MyStruct ms = list[0]; 4ms.Name = "MyStruct42"; 5list[0] = ms;

↑でエラーとならない理由
ここがよくわかりません。
②でも①と同様にms.NameのGetterから取り出した値に対して
代入をしているように見えてしまいます。

アドバイスお願いいたします。

hqf00342👍を押しています

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

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

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

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

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

juner

2024/03/14 06:14

ベストアンサーを選択していないということは現在の回答の内容では理解の一助にならないという意味合いでしょうか……?
guest

回答6

0

こんにちは。

まず、① のコードは ② と等価ではありません。
① のコードを ② っぽく書くと以下のようになります。

csharp

1List<MyStruct> list = {}; 2 3{ 4 MyStruct ms = list[0]; 5 ms.Name = "MyStruct42"; 6}

② との違いは、list に入れ直す処理 list[0] = ms; が無いことです。
せっかく ms に書き込んだのに、それを何もせず捨てているわけですね。
list の中身は変更されることはなく、書いたコードに意味はありません。

なので、そういう罠を踏まないように、わざわざエラーにしてくれているのです。親切ですね。

投稿2024/02/15 06:34

tamoto

総合スコア4110

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

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

0

MyStruct は構造体(=値型) なので、list[0].Name の段階で list[0] からのコピーの MyStruct.Name が呼ばれている為、そこに代入しても意味が無いというエラーです。

list[0] を直接修正したい場合は次の様に CollectionsMarshal.AsSpan() すると List<T> の内部配列を取得でき、そこから インデクサアクセスにより list[0] 内の参照が取得できる為、戻し忘れみたいなこともないのでおすすめします。(こういう時に配列を使うと list[0] がref 戻り値なので直接 参照が取得できるので楽でもあります。

csharp

1using System; 2using System.Collections.Generic; 3using System.Runtime.InteropServices; 4List<MyStruct> list = [ new("a"), new("b"), new("c")]; 5 6Console.WriteLine($"[{string.Join(", ", list)}]"); 7 8// 内部配列を取得 9var span = CollectionsMarshal.AsSpan(list); 10// 変更 11span[0].Name = "MyStruct42"; 12 13Console.WriteLine($"[{string.Join(", ", list)}]"); 14 15 16struct MyStruct { 17 public MyStruct(string name) => Name = name; 18 public string Name; 19 public override string ToString() => $"{nameof(MyStruct)}{{ {nameof(Name)} = {Name}}}"; 20}

sharplab

実行結果

text

1[MyStruct{ Name = a}, MyStruct{ Name = b}, MyStruct{ Name = c}] 2[MyStruct{ Name = MyStruct42}, MyStruct{ Name = b}, MyStruct{ Name = c}]

蛇足

配列ならこう

csharp

1using System; 2MyStruct[] list = [ new("a"), new("b"), new("c")]; 3 4Console.WriteLine($"[{string.Join(", ", list)}]"); 5 6// 変更 7list[0].Name = "MyStruct42"; 8 9Console.WriteLine($"[{string.Join(", ", list)}]"); 10 11struct MyStruct { 12 public MyStruct(string name) => Name = name; 13 public string Name; 14 public override string ToString() => $"{nameof(MyStruct)}{{ {nameof(Name)} = {Name}}}"; 15}

sharplab

投稿2024/02/15 08:07

編集2024/02/26 04:47
juner

総合スコア128

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

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

0

関数の戻り値とはreturnに指定された変数などが持つ値のコピーを指します
参照型(class)と値型(strict)では、このコピーされる値が異なります

参照型の場合、戻り値にコピーされる値はインスタンスの場所を指し示すアドレスなので、returnに指定された値と同一のインスタンスを指すことができます

戻り値経由でインスタンスのメンバ値を変更できるのはこのためです

一方で値型の場合はreturnに指定される値がオブジェクトのインスタンスそのものなので戻り値もそのコピーとなります

この時、returnに指定されたオブジェクトは関数の終了によって変数などと共に解放されます
よって戻り値経由でインスタンスへアクセスしても、それは元のインスタンスとは異なる上、オリジナルが存在しません

変更の反映先が不在となるため、このような操作はコンパイラにより無意味と判定されてエラーとなります

そして今回、listを通じて返されているのはNameという名のプロパティです
プロパティはgetsetの二つのメソッドで構成される関数の一種です
よってプロパティから返される値はgetでreturnされたインスタンスのコピーとなります

さて、これを踏まえて以下のコードを解剖します

C#

12List<MyStruct> list = {}; 3MyStruct ms = list[0]; 4ms.Name = "MyStruct42"; 5list[0] = ms;

先程プロパティはgetsetの二つのメソッドを持つと紹介しました
ms.Name = "MyStruct42"Nameプロパティのsetに値を渡す宣言です

setにはvalueという引数があります
Nameに代入された値は全てsetメソッドで管理されます

MyStruct ms = list[0]; は新規に型宣言された変数mslist[0]が持つインスタンスのコピーを渡す宣言です
そしてlist[0] = msではlist[0]msが持つインスタンスをコピーしています

つまりlist[0]からコピーして編集を加えたインスタンスを再びlist[0]にコピーし直しています
ここで行われていることは全てコピーによる値の取得と上書きなので、エラーになる要素がありません

投稿2024/02/15 10:52

編集2024/02/15 10:56
Manabu

総合スコア25

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

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

ozwk

2024/02/15 12:12

> そして今回、listを通じて返されているのはNameという名のプロパティです Nameがプロパティかは今回関係ないと思います
Manabu

2024/02/15 21:46

ああ...確かに プロパティの理解は重要ですが、今回はlist[0]がList<T>に属するpublic T this[int]というプロパティのgetterを呼び出す宣言であることの方がポイントですね
guest

0

この問題のポイントは、MyStructが、クラスではなく、構造体であることです。

その為、MyStruct ms = list[0]; は、参照ではなく値のコピーとなります。
これは、Listとは別の場所なので、ms.Name = "MyStruct42"; で代入しても、CS1612は発生しません。

なお、別の場所での代入なので、最後の list[0] = ms; を忘れると、List内は変更されません。

投稿2024/02/15 07:03

YT0014

総合スコア1708

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

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

juner

2024/04/12 08:24 編集

list が配列であれば ref MyStruct ms = ref list[0]; で参照にできたのですけれどね。 (※ 配列であれば list[0].Name = "test"; で直接構造体の操作が利く
guest

0

list[0].Name部分はlist[0].NameのGetterから取得してきた値

②でも①と同様にms.NameのGetterから取り出した値

どういう意味で言っているのかよくわかりませんが,
問題は Name ではなく list[0] の側です.

ここでは list の要素が値側なので list[0]list に入っている最初の要素そのものではなくてそのコピーを返す,という話です.
返ってきたコピーを全く使うそぶりが無いのにそのコピーの中身を変更しても意味が無い,という旨のエラーでしょう.

投稿2024/02/15 06:58

fana

総合スコア11658

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

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

0

まずエラーとなるほうですが
Listに格納されている構造体をAとします。
そこでListからlist[0]と構造体を取得する時に実際に格納されているAを複製したA'が返されます。
そうなるとlist[0].Name = "MyStruct42";というのはA'.Name = "MyStruct42";となり、そのままA'という構造体は保持されていないので消えてしまいます。値を変数に受けずにそのものを変更しようとしているのでエラーとなります。

エラーとならないほうですが
MyStruct ms = list[0]; と複製されたA'がmsという変数に格納されます。
その後、ms.Name = "MyStruct42";A'の内容が変更されます。
そしてlist[0] = ms;とListにA'を代入しようとします。この際A'が複製されListにはA''が格納されることになります。

投稿2024/02/15 06:56

YAmaGNZ

総合スコア10258

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問