Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9703

【C#】「参照型」を渡しても「参照渡し」にはならない件

$
0
0

はじめに

参照型とか参照渡しとかごっちゃになりそうだったので自分なりに整理してみた結果、表題の通り「『参照型』を渡したからといって、『参照渡し』にはなっていない」ということに気づきました。
以下に整理してみます。

「参照型」と「参照渡し」は違う

クラスなどの「参照型」ならば「参照渡し」になると思っていませんか?
というか僕は思っていたんですけどね。全く違いました。

「参照型」とは、変数が「インスタンスへの参照」を示す型のことです。
「参照渡し」とは、渡したいものの「参照」を渡す渡し方のことです。

「参照型」とは「型の性質」であり、「参照渡し」とは「インスタンスの引き渡し方法」なので、それらは互いに関係し合いません。
なので当然、「参照型」ならば「参照渡し」である、とはならないわけです。

「値型」と「値渡し」

対するものとして、「値型」と「値渡し」があります。
「値型」とは、変数が「インスタンスそのもの」を示す型のことです。
「値渡し」とは、渡したいものの「コピー」を渡す渡し方のことです。

そして、C#では基本的に「参照渡し」ではなく「値渡し」となります。
したがって、「参照型」のインスタンスを普通に渡した場合、「参照型の値渡し」となります。参照型だからといって、参照渡しにはなりません。

参照型の「値渡し」

「参照型の値渡し」とはどういうことかを上の説明に当てはめて考えてみると、《「変数が『インスタンスへの参照』を示す型」の変数を、「渡したいもののコピーを渡す方法で渡す」》ということになります。
この場合、「渡したいもの」とは「インスタンスへの参照」となりますので、「インスタンスへの参照のコピーを渡す」ということになりますね。

参照型の値渡し.png

※本来、参照型の実体はヒープに置かれますがわかりやすくするためクラスAの中に書いています

参照型の「参照渡し」

C#では基本的に値渡しとなるわけですが、御存知の通りref修飾子を使えば「参照渡し」をすることもできます。
では、「参照型」に対して「参照渡し」、つまり「参照型の参照渡し」をするとどうなるのでしょうか?

また上の説明に当てはめて考えてみると、《「変数が『インスタンスへの参照』を示す型」の変数を、「『渡したいものの参照』を渡す方法で渡す」》ということになります。
この場合、「渡したいもの」とは「インスタンスへの参照」なので、「インスタンスへの参照の参照を渡す」ということになります。

参照型の参照渡し.png

つまり、もともと「インスタンスへの参照」だったものへのさらに深い2重参照をしていることになります。

このように、「参照型」でも「値渡し」「参照渡し」の2通りの渡し方で渡すことができ、それぞれ内部で異なる処理がされていることがわかりました。
では、「参照型を値渡しした場合」と、「参照型を参照渡しした場合」とで、実際の挙動はどう違うのでしょうか?

「参照型の値渡し」と「参照型の参照渡し」の挙動の違い

「参照型の値渡し」と「参照型の参照渡し」との違いとしては、渡った先でインスタンス自体を書き換えられるかどうかが挙げられます。

「参照型の値渡し」では、「インスタンスへの参照のコピー」が渡されるのでした。
参照とはいえコピーなので、それ自体を別のものに書き換えたところで、コピー元には何も影響がありません。

しかし、「参照型の参照渡し」では、「インスタンスへの参照の参照」が渡されるのでした。
渡されているものは「参照」なので、それを別のものに書き換えれば、当然元データもそのものに書き換えられてしまいます。

これが「参照型の値渡し」と「参照型の参照渡し」との挙動の違いになります。
では、実際にどのように挙動の違いがあるか試してみます。

挙動の違いを実際に試して実感する

「参照型の値渡し」と「参照型の参照渡し」との挙動の違いを、以下のソースコードで試してみます。
比較のために、「値型の値渡し」と「値型の参照渡し」も同時に挙動の違いを確認します。

classProgram{staticvoidMain(string[]args){//参照型と値型のインスタンスを4つずつ用意varvals=Enumerable.Repeat(0,4).Select(_=>newValueStruct("初期値")).ToArray();varrefs=Enumerable.Repeat(0,4).Select(_=>newReferenceClass("初期値")).ToArray();//値型を値渡しでプロパティ書き換えOverwriteProperty(vals[0]);//値型を参照渡しでプロパティ書き換えOverwriteProperty(refvals[1]);//値型を値渡しでインスタンス書き換えReplaceInstance(vals[2]);//値型を参照渡しでインスタンス書き換えReplaceInstance(refvals[3]);//参照型を値渡しでプロパティ書き換えOverwriteProperty(refs[0]);//参照型を参照渡しでプロパティ書き換えOverwriteProperty(refrefs[1]);//参照型を値渡しでインスタンス書き換えReplaceInstance(refs[2]);//参照型を参照渡しでインスタンス書き換えReplaceInstance(refrefs[3]);foreach(variteminvals){Console.WriteLine(item.Message);}foreach(variteminrefs){Console.WriteLine(item.Message);}Console.ReadKey();}staticvoidOverwriteProperty(ValueStruct@struct){@struct.Message="値型を値渡しでプロパティ書き換えました";}staticvoidOverwriteProperty(refValueStruct@struct){@struct.Message="値型を参照渡しでプロパティ書き換えました";}staticvoidOverwriteProperty(ReferenceClass@class){@class.Message="参照型を値渡しでプロパティ書き換えました";}staticvoidOverwriteProperty(refReferenceClass@class){@class.Message="参照型を参照渡しでプロパティ書き換えました";}staticvoidReplaceInstance(ValueStruct@struct){@struct=newValueStruct("値型を値渡しでインスタンス置き換えました");}staticvoidReplaceInstance(refValueStruct@struct){@struct=newValueStruct("値型を参照渡しでインスタンス置き換えました");}staticvoidReplaceInstance(ReferenceClass@class){@class=newReferenceClass("参照型を値渡しでインスタンス置き換えました");}staticvoidReplaceInstance(refReferenceClass@class){@class=newReferenceClass("参照型を参照渡しでインスタンス置き換えました");}}/// <summary>/// 参照型/// </summary>classReferenceClass{publicReferenceClass(stringmessage){this.Message=message;}publicstringMessage{get;set;}}/// <summary>/// 値型/// </summary>structValueStruct{publicValueStruct(stringmessage){this.Message=message;}publicstringMessage{get;set;}}

このソースコードが何をしているかというと、①「インスタンスの中身(プロパティ)の書き換え」と②「インスタンス自体の書き換え」という2つの操作を、「値型の値渡し」「値型の参照渡し」「参照型の値渡し」「参照型の参照渡し」という4種類の渡し方で試して、元データにどのような影響があるかを確認しています。

結果は以下のようになりました。「初期値」となっているものは、元データへの影響がないことを示しています。

初期値
値型を参照渡しでプロパティ書き換えました
初期値
値型を参照渡しでインスタンス置き換えました
参照型を値渡しでプロパティ書き換えました
参照型を参照渡しでプロパティ書き換えました
初期値
参照型を参照渡しでインスタンス置き換えました

この結果をまとめると以下のようになります。
※✕が書き換えできなかったことを示します

プロパティ書き換えインスタンス書き換え
値型値渡し
値型参照渡し
参照型値渡し
参照型参照渡し

値型の場合は想像通りですね。値渡しをすれば、渡った先で何をされようが元データには影響ありません。コピーが渡っているからですね。

参照型の場合は、値型と違って値渡しでもプロパティの書き換えが可能です。しかし、値渡しではインスタンス自体の書き換えはできなくなっています
これは、「値渡し」とはあくまでも「渡したいもののコピー」を渡す渡し方なので、「参照のコピー」に対して別のものに書き換えを行ったところで、「元の参照」には何も影響がないためです。
「参照型の参照渡し」を行うと、「渡したいものの参照」を渡すので、「参照の参照」が引き渡されるため、引き渡し先でインスタンス自体を書き換えると、元の参照にも影響が及ぶというわけですね。

さいごに

参照型と参照渡しの関係性について、ごちゃごちゃになっていたので自分なりに整理してみました。
情報が間違っていたらご指摘等お待ちしています。

[11/23追記]
早速ご指摘いただきました。
「変数」のことを「インスタンス」と表現してしまっていました。お詫びして修正します。


Viewing all articles
Browse latest Browse all 9703

Trending Articles