refを使用する場合とC#で不要な場合


104

プログラムのメモリ内状態であるオブジェクトがあり、状態を変更するためにオブジェクトを渡す他のいくつかのワーカー関数もあります。私はそれをワーカー関数に参照渡ししています。しかし、私は次の機能に出くわしました。

byte[] received_s = new byte[2048];
IPEndPoint tmpIpEndPoint = new IPEndPoint(IPAddress.Any, UdpPort_msg);
EndPoint remoteEP = (tmpIpEndPoint);

int sz = soUdp_msg.ReceiveFrom(received_s, ref remoteEP); 

received_sremoteEPが関数から何かを返すため、混乱を招きます。なぜがremoteEP必要で、必要refreceived_sはないのですか?

私はACプログラマーでもあるので、頭からポインターを取得するのに問題があります。

編集:C#のオブジェクトは、内部のオブジェクトへのポインターのようです。したがって、オブジェクトを関数に渡すと、ポインタを介してオブジェクトの内容を変更でき、関数に渡されるのはオブジェクトへのポインタだけなので、オブジェクト自体はコピーされません。二重ポインタのような関数でスイッチアウトしたり、新しいオブジェクトを作成したりしたい場合は、refまたはoutを使用します。

回答:


216

短い答え:引数渡しに関する私の記事を読んでください

長い答え:参照型パラメーターが値で渡される場合、参照のみが渡され、オブジェクトのコピーは渡されません。これは、CまたはC ++で(値によって)ポインターを渡すのに似ています。パラメータ自体の値の変更は、呼び出し側から見たが、基準点は、対象物の変更されないであろう見られます。

(あらゆる種類の)パラメータが渡されたときにより手段は、パラメータの変更は、発信者が見ていることをことを、参照-パラメータの変更がされている変数への変更。

記事では、これらすべてについてさらに詳しく説明します。もちろん:)

便利な答え:ref / outを使用する必要はほとんどありません。これは基本的には別の戻り値を取得する方法であり、メソッドが多すぎることを実行しようとしていることを意味するため、通常は正確に回避する必要があります。常にそうであるとは限りません(TryParseなどは、の適切な使用法の標準的な例ですout)ref / outの使用は比較的まれです。


38
短い答えと長い答えが混在していると思います。それは大きな記事です!
アウトロープログラマー

23
@Outlaw:はい。ただし、記事を読むための指示である短い回答自体は、6ワードしかありません:)
Jon Skeet

27
リファレンス!:)
ゴンゾブレイン

5
@Liamのようにrefを使用すると、より明確に見えるかもしれませんが、他のプログラマー(とにかくそのキーワードが何をするかを知っているプログラマー)を混乱させる可能性があります。呼び出しメソッドで使用されます。つまり、別のオブジェクト(または、可能であればnullにさえ)に再割り当てするので、それに固執しないでください。これはかなり強力で、「オブジェクトを変更できる」とは完全に異なります。これは、オブジェクト参照をパラメーターとして渡すときは常にそうです。
mbargiel 2014

1
@ M.Mimpen:C#7は(うまくいけば)許可しますif (int.TryParse(text, out int value)) { ... use value here ... }
Jon Skeet

26

非refパラメーターはポインターであり、refパラメーターは二重ポインターであると考えてください。これは私に最も役立ちました。

参照によって値を渡すことはほとんどありません。相互運用性の問題がなければ、.Netチームはそれを元の仕様に含めなかったと思います。参照パラメーターが解決するほとんどの問題に対処するオブジェクト指向の方法は、次のとおりです。

複数の戻り値の場合

  • 複数の戻り値を表す構造体を作成します

メソッド呼び出しの結果としてメソッド内で変化するプリミティブの場合(メソッドはプリミティブパラメーターに副作用があります)

  • オブジェクトにメソッドをインスタンスメソッドとして実装し、メソッド呼び出しの一部としてオブジェクトの状態(パラメーターではない)を操作する
  • 複数の戻り値ソリューションを使用して、戻り値を状態にマージします
  • メソッドで操作できる状態を含むオブジェクトを作成し、プリミティブ自体ではなく、そのオブジェクトをパラメーターとして渡します。

8
良い神。私はそれを理解するためにこの20倍を読まなければならないでしょう。単純なことをするだけで、余計な手間がかかるように思えます。
PositiveGuy

.NETフレームワークは、#1のルールに従いません。( '複数の戻り値の場合は構造体を作成します')。例を挙げましょうIPAddress.TryParse(string, out IPAddress)
Swen Kooij

@SwenKooijそうですね。パラメータを使用するほとんどの場所で、(a)Win32 APIをラップしている、または(b)初期の頃で、C ++プログラマが意思決定をしていたと思います。
Michael Meadows 2014

@SwenKooij私はこの返信が何年も遅れていることを知っていますが、実際には遅れています。私たちはTryParseに慣れていますが、それが良いとは限りません。いうよりもいればもっと良かったはずif (int.TryParse("123", out var theInt) { /* use theInt */ }、私たちが持っていたvar candidate = int.TrialParse("123"); if (candidate.Parsed) { /* do something with candidate.Value */ }それはより多くのコードですが、C#言語の設計とはるかに一貫性があります。
Michael Meadows

9

おそらく、C#アプリ全体を記述し、参照によってオブジェクト/構造体を渡すことはありません。

私はこれを私に言った教授がいました:

refsを使用する唯一の場所は、次のいずれかです。

  1. 大きなオブジェクトを渡したい場合(つまり、オブジェクト/構造体の中にオブジェクト/構造体が複数のレベルにある場合)、それをコピーするとコストがかかり、
  2. フレームワーク、Windows API、またはそれを必要とするその他のAPIを呼び出しています。

できるからといってそれをしないでください。パラメータの値の変更を開始し、注意を払っていない場合は、厄介なバグによってお尻に食い込む可能性があります。

私は彼の助言に同意します。学校から5年以上たった今、フレームワークやWindows APIを呼び出す以外にその必要はありませんでした。


3
「スワップ」の実装を計画している場合は、参照渡しをすると役立つ場合があります。
ブライアン

@Chris小さなオブジェクトにrefキーワードを使用すると問題が発生しますか?
ManirajSS 2014

@TechnikEmpire「しかし、呼び出された関数のスコープ内のオブジェクトへの変更は、呼び出し元に反映されません。」それは間違いです。Personをに渡すSetName(person, "John Doe")と、nameプロパティが変更され、その変更が呼び出し元に反映されます。
M.ミンペン、2016年

@ M.Mimpenコメントが削除されました。私はその時点ではC#をほとんど選択しておらず、* $$をはっきりと話していました。注目していただきありがとうございます。

@Chris-「大きな」オブジェクトを渡すことは高価ではないと確信しています。値で渡すだけでも、単一のポインタを渡すだけですか?
Ian

3

received_sは配列なので、その配列へのポインターを渡します。関数は、既存のデータを操作し、基になる場所やポインターを変更しません。refキーワードは、実際のポインターを場所に渡し、そのポインターを外部関数で更新することを示しているため、外部関数の値が変更されます。

たとえば、バイト配列は、メモリが更新された前後の同じメモリへのポインタです。

エンドポイント参照は、実際には、外部関数のエンドポイントへのポインターを、関数内で生成された新しいインスタンスに更新しています。


3

refは、ポインタを参照で渡すことを意味すると考えてください。refを使用しないということは、値によってポインターを渡すことを意味します。

さらに良いことに、私が今言ったことを無視し(特に値の型に関しては誤解を招く可能性があります)、このMSDNページを読んでください


実際、そうではありません。少なくとも後編。参照型は、refを使用するかどうかに関係なく、常に参照によって渡されます。
Erik Funkenbusch 2009年

実際には、さらに考えてみると、それは正しく出てこなかった。参照は実際には、参照型なしで値によって渡されます。つまり、参照が指す値を変更すると元のデータが変更されますが、参照自体を変更しても元の参照は変更されません。
Erik Funkenbusch 2009年

2
非ref参照タイプは参照によって渡されません。参照タイプへの参照は値で渡されます。しかし、参照が参照されているものへのポインタとして考えたい場合、私が言ったことは意味があります(しかし、そのように考えることは誤解を招く可能性があります)。したがって、私の警告。
ブライアン


0

私の理解では、Objectクラスから派生したすべてのオブジェクトはポインターとして渡されますが、通常の型(int、struct)はポインターとして渡されず、refが必要です。文字列がわからない(最終的にObjectクラスから派生したものですか?)


これはいくつかの説明を使用することができます。値と参照の違いは何ですか?なぜそれがパラメーターにrefキーワードを使用することに関連するのですか?
oɔɯǝɹ

.netでは、ポインター、型パラメーター、およびインターフェースを除くすべてがオブジェクトから派生しています。「通常の」型(正しくは「値の型」と呼ばれる)もオブジェクトから継承することを理解することが重要です。あなたはそれから正しいです:値型(デフォルト)は値によって渡されますが、参照型は参照によって渡されます。新しい値を返すのではなく(メソッド内で参照型のように処理する)値型を変更する場合は、その値にrefキーワードを使用する必要があります。しかし、それは悪いスタイルであり、絶対にそうしなければならないことが確実でない限り、そうすべきではありません:)
buddybubble

1
あなたの質問に答える:文字列はオブジェクトから派生します。これは値型のように動作する参照型です(パフォーマンスと論理上の理由から)
buddybubble

0

Jon Skeetの回答全体と他のいくつかの回答には同意しますが、を使用するユースケースがrefあり、それはパフォーマンスの最適化を強化するためのものです。パフォーマンスプロファイリング中に、メソッドの戻り値を設定するとパフォーマンスに若干の影響があることが観察されていrefますが、戻り値が引数として入力される引数として使用すると、このわずかなボトルネックが解消されます。

これは、最適化の取り組みが極端なレベルで行われ、ミリ秒またはスプリットミリ秒を節約するために可読性とおそらくテスト可能性と保守性を犠牲にする場合にのみ、本当に役立ちます。


-1

最初にグラウンドゼロルール。プリミティブは値(スタック)によって渡され、非プリミティブは関連するTYPESのコンテキストで参照(ヒープ)によって渡されます。

関連するパラメーターは、デフォルトでValueによって渡されます。詳細を説明する良い記事。 http://yoda.arachsys.com/csharp/parameters.html

Student myStudent = new Student {Name="A",RollNo=1};

ChangeName(myStudent);

static void ChangeName(Student s1)
{
  s1.Name = "Z"; // myStudent.Name will also change from A to Z
                // {AS s1 and myStudent both refers to same Heap(Memory)
                //Student being the non-Primitive type
}

ChangeNameVersion2(ref myStudent);
static void ChangeNameVersion2(ref Student s1)
{
  s1.Name = "Z"; // Not any difference {same as **ChangeName**}
}

static void ChangeNameVersion3(ref Student s1)
{
    s1 = new Student{Name="Champ"};

    // reference(myStudent) will also point toward this new Object having new memory
    // previous mystudent memory will be released as it is not pointed by any object
}

(警告付きで)非プリミティブ型はポインタに過ぎない と言えます。refでそれらを渡すと、ダブルポインタを渡していると言えます。


すべてのパラメーターは、デフォルトではC#で値によって渡されます。 参照によって任意のパラメーターを渡すことができます。参照型の場合、渡される値(参照または値)はそれ自体が参照です。それは、それがどのように渡されるかとは完全に独立しています。
サービー2014

同意して@Servy!「使用されている「参照」または「値」という言葉を聞くとき、パラメーターが参照または値パラメーターであることを意味していたのか、関係するタイプが参照または値タイプであることを意味しているのかを非常に明確にする必要があります。私の側の混乱、最初にグラウンドゼロルールを言ったとき、プリミティブは値(スタック)によって渡され、非プリミティブは参照(ヒープ)によって渡されます。 、すべてのパラメーターはデフォルトでC#で値によって渡されます
Surender Singh Malik

パラメータが参照または値パラメータであると言うことは標準的な用語ではなく、あなたが何を意味するかについては本当にあいまいです。パラメータのタイプは、参照タイプまたは値タイプのいずれかです。パラメータは、値または参照によって渡すことができます。これらは、特定のパラメーターの直交する概念です。あなたの投稿はこれについて誤って説明しています。
サービー2014

1
パラメータは参照または値で渡します。「参照による」および「値による」という用語は、型が値型であるか参照型であるかを説明するために使用されていません。
サービー2014

1
いいえ、それは知覚の問題でありません。あなたが使用している場合、誤った用語を何かに参照するために、その文はなり間違っています。正しい説明では、概念を参照するために正しい用語を使用することが重要です。
サービー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.