refと実行時のoutの違いは何ですか?


15

C#は、参照によって渡される引数を作成するためのrefand outキーワードを提供します。2つのセマンティックは非常に似ています。唯一の違いは、フラグ付き変数の初期化です。

  • ref関数に渡す前に変数を初期化する必要がありますが、必要ありoutません。
  • out関数内で変数を初期化する必要がありますが、必要ありrefません。

これらの2つのキーワードの使用例もほぼ同じであり、あまりにも頻繁に使用されるため、コードのにおいと見なされます(TryParseandやTryGetValuepattern などの有効な使用例もあります)。

このため、誰かが説明できますが、なぜC#には非常に狭いユースケース用の2つの非常に類似したツールがあるのですか?

また、MSDNでは、実行時の動作が異なることが記載されています。

refキーワードとoutキーワードは異なる実行時の動作を引き起こしますが、コンパイル時のメソッドシグネチャの一部とは見なされません。

実行時の動作はどのように異なりますか?

結論

両方の答えが正しいように見えます、ありがとう。jmorenoを受け入れたのは、より明確だからです。


私の推測では、どちらもrefC ++ と同じ方法で実装されています。問題のオブジェクトポインター(またはプリミティブ)へのポインター。すなわち、Int32.TryParse(myStr, out myInt)(C#)はint32_tryParse(myStr, &myInt)(C)と同じ方法で「実行」されます。唯一の違いは、バグを防ぐためにコンパイラによって強制されるいくつかの制約です。(これが舞台裏でどのように機能するかについて間違っている可能性があるため、これを回答として投稿するつもりはありませんが、これは(理にかなっているので)動作を想像する方法です)
コールジョンソン

回答:


10

この質問が行われた時点で、MSDNの記事は微妙に間違っていました(その後修正されました)。「異なる動作を引き起こす」の代わりに、「異なる動作が必要」です。

特に、同じメカニズム(IL)を使用して動作を有効にしている場合でも、コンパイラは2つのキーワードに異なる要件を適用します。


ですから、私の理解では、コンパイラはoutパラメータの逆参照やrefパラメータの割り当てを行わないなどのことをチェックしますが、それが発行するILはいずれにしても参照にすぎません。あれは正しいですか?
アンドリューは、モニカを復活させる

ところで、人はいるという事実からこれを言うかもしれないout Tし、ref TそのようVB.NetまたはC ++ / CLIなどの他のCLI言語で同じようにレンダリングされています。
ACH

@AndrewPiliser:正しい。コードがどちらかの方法でコンパイルされると仮定すると(そうすべきではないときに逆参照しようとしないでください)、ILは同一になります。
-jmoreno

4

ここにあなたの質問に答えることができるトピックに関する興味深い記事があります:

http://www.dotnetperls.com/ref

興味のあるポイント:

「refとoutの違いは、共通言語ランタイムではなく、C#言語自体にあります。」

更新:

私の仕事の上級開発者は@jmorenoの答えを裏付けたばかりなので、必ず読んでください!


したがって、正しく理解すれば、ILレベルに違いはありません。これは、異なる実行時の動作を持たせられないことを意味します。これは、MSDNの言うことと矛盾します。それでも興味深い記事!
ガボールアンギャル

@GáborAngyal偶然、MSDNの記事へのリンクをたまたま持っていませんか?意見が異なる場合はここにいるといいかもしれません
ダンボーリュー

ここに行きます:msdn.microsoft.com/en-gb/library/t3c3bfhx.aspxですが、私の質問にはずっとありました:)
ガボールアンギャル

1
stringこれらすべてとは何の関係があるのでしょうか?
ロバートハーヴェイ

これは、この記事の単なる言い換えです。二行目..削除するように更新
ダン・ボーリュー

2

ランタイム?まったくありません。refまたはoutキーワードのみが異なる同じパラメーターを持つメソッドをオーバーロードすることはできません。

これをコンパイルしようとすると、「同じ署名のメソッドが既に宣言されています」というコンパイルエラーが表示されます。

    private class MyClass
    {
        private void DoSomething(out int param)
        {
        }
        private void DoSomething(ref int param)
        {
        }
    }

この質問に答えるには: 「... C#に非常に類似した2つの非常に狭いユースケースのツールがあるのはなぜですか?」

コードの読みやすさとAPIの観点からは、大きな違いがあります。APIのコンシューマーとして、「out」が使用される場合、APIはoutパラメーターに依存しないことを知っています。API開発者として、「out」を好み、絶対に(まれに!)必要な場合にのみ「ref」を使用します。すばらしい議論については、このリファレンスを参照してください。

/programming/1516876/when-to-use-ref-vs-out

サポート情報:次のメソッドをコンパイルし、分解しました。refキーワードとoutキーワード(この例ではout)を使用しましたが、予想どおりアドレス参照を除いてアセンブリコードは変更されませんでした。

    private class MyClass
    {
        internal void DoSomething(out int param)
        {
            param = 0;
        }
    }

00000000 push ebp
00000001 mov ebp、esp
00000003 push edi
00000004 push esi
00000005 push ebx
00000006 sub esp、34h
00000009 xor eax、eax
0000000b mov dword ptr [ebp-10h]、eax
0000000e mov dword ptr [ebp-1Ch]、eax
00000011 mov dword ptr [ebp-3Ch]、ecx
00000014 mov dword ptr [ebp-40h]、edx
00000017 cmp dword ptr ds:[008B1710h]、0
0000001e je 00000025
00000020 call 6E6B601E
00000025 nop
param = 0;
00000026 mov eax、dword ptr [ebp-40h]
00000029 xor edx、edx
0000002b mov dword ptr [eax]、edx
}
0000002d nop
0000002e lea esp、[ebp-0Ch] 00000031 pop
ebx
00000032 pop esi
00000033 pop edi
00000034 pop ebp
00000035 ret

アセンブリを正しく読んでいますか?


記述内容は正しいですが、このオーバーロードは許可されていないため、実行時の違いがないことは明らかな含意ではないことに注意してください。
ガボールアンギャル

ああ-良い点!誰でも上記のコードを逆アセンブルして、正しいか間違っているかを証明できますか?私はアセンブリを読むのはひどいですが、知りたいです。
シュモケン

大丈夫-私は「アウト」と「参照」を使用して、コードを逆アセンブルし、私は私のdoSomethingのメソッドのいずれかの違いを見ることができない:
Shmoken
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.