9

C# では、メソッドのパラメーターは参照型または値型のいずれかになります。参照型を渡す場合、参照のコピーが渡されます。このように、メソッド内で渡された参照を別のオブジェクト インスタンスに再割り当てしようとすると、メソッドの外では再割り当ては表示されません。

これを機能させるために、C# には ref 修飾子があります。ref を使用して参照型を渡すと、実際にはコピーではなく元の参照が使用されます。(私が間違っている場合は修正してください)。

この場合、参照のコピーを作成していないので、メモリを節約できますか? メソッドが広範囲に呼び出されると、アプリケーションの全体的なパフォーマンスが向上しますか?

ありがとう!

4

4 に答える 4

17

請求

いいえ、そうではありません。どちらかといえば、余分なルックアップのために遅くなります。

後で特に割り当てるつもりがない限り、参照型を参照渡しする理由はありません。


証拠

コンパイラが「変数自体」を渡すと考える人もいるようなので、このコードの逆アセンブルを見てください。

using System;

static class Program
{
    static void Test(ref object o) { GC.KeepAlive(o); }

    static void Main(string[] args)
    {
        object temp = args;
        Test(ref temp);
    }
}

これは(簡単にするためにx86で):

// Main():
// Set up the stack
00000000  push        ebp                    // Save the base pointer
00000001  mov         ebp,esp                // Set up stack pointer
00000003  sub         esp,8                  // Reserve space for local variables
00000006  xor         eax,eax                // Zero out the EAX register

// Copy the object reference to the local variable `temp` (I /think/)
00000008  mov         dword ptr [ebp-4],eax  // Copy its content to memory (temp)
0000000b  mov         dword ptr [ebp-8],ecx  // Copy ECX (where'd it come from??)
0000000e  cmp         dword ptr ds:[00318D5Ch],0  // Compare this against zero
00000015  je          0000001C               // Jump if it was null (?)
00000017  call        6F910029               // (Calls some internal method, idk)

// THIS is where our code finally starts running
0000001c  mov         eax,dword ptr [ebp-8]  // Copy the reference to register
0000001f  mov         dword ptr [ebp-4],eax  // ** COPY it AGAIN to memory
00000022  lea         ecx,[ebp-4]            // ** Take the ADDRESS of the copy
00000025  call        dword ptr ds:[00319734h] // Call the method

// We're done with the call
0000002b  nop                                // Do nothing (breakpoint helper)
0000002c  mov         esp,ebp                // Restore stack
0000002e  pop         ebp                    // Epilogue
0000002f  ret                                // Return

これは、コードの最適化されたコンパイルによるものです。明らかに、「変数自体」ではなく、変数のアドレスが渡されます。

于 2011-08-24T07:24:44.597 に答える
6

Mehrdad の例の DISSASEMBLER VIEW (両方のバージョン)

私のようにアセンブリ コードを読むのが苦手な人のために、Mehrdad の優れた証明をもう少し掘り下げてみます。このコードは、デバッグ中に Visual Studio でキャプチャできます。[Debug] -> [Windows] -> [Dissasembly] をクリックします。

REFを使用したバージョン

ソースコード:

 namespace RefTest
 {
    class Program
    {
        static void Test(ref object o) { GC.KeepAlive(o); }

        static void Main(string[] args)
        {
            object temp = args;
            Test(ref temp);
        }
    }
 }

アセンブリ言語 (x86) (異なる部分のみ表示):

             object temp = args;
 00000030  mov         eax,dword ptr [ebp-3Ch] 
 00000033  mov         dword ptr [ebp-40h],eax 
             Test(ref temp);
 00000036  lea         ecx,[ebp-40h] //loads temp address's address on ecx? 
 00000039  call        FD30B000      
 0000003e  nop              
         }  

参照なしのバージョン

ソースコード:

 namespace RefTest
 {
    class Program
    {
        static void Test(object o) { GC.KeepAlive(o); }

        static void Main(string[] args)
        {
            object temp = args;
            Test(temp);
        }
    }
 }

アセンブリ言語 (x86) (異なる部分のみ表示):

             object temp = args;
 00000035  mov         eax,dword ptr [ebp-3Ch] 
 00000038  mov         dword ptr [ebp-40h],eax 
             Test(temp);
 0000003b  mov         ecx,dword ptr [ebp-40h] //move temp address to ecx?
 0000003e  call        FD30B000 
 00000043  nop              
         }

コメント行を除いて、コードは両方のバージョンで同じです。ref がある場合、関数の呼び出しの前に LEA 命令があり、ref がない場合は、より単純な MOV 命令になります。この行を実行した後、LEA はオブジェクトへのポインターへのポインターを使用して ecx レジスターをロードしましたが、MOV はオブジェクトへのポインターを使用して ecx をロードしました。これは、最初のケースの FD30B000 サブルーチン (Test 関数を指している) が、オブジェクトに到達するためにメモリへの追加アクセスを行わなければならないことを意味します。この関数の生成された各バージョンのアセンブリ コードを調べると、ある時点 (実際には 2 つのバージョン間で異なる唯一の行) で余分なアクセスが行われていることがわかります。

static void Test(ref object o) { GC.KeepAlive(o); }
...
00000025  mov         eax,dword ptr [ebp-3Ch] 
00000028  mov         ecx,dword ptr [eax]
...

refのない関数はオブジェクトに直接行くことができますが:

static void Test(object o) { GC.KeepAlive(o); }
...
00000025  mov         ecx,dword ptr [ebp-3Ch]
...

それが役に立ったことを願っています。

于 2011-08-25T09:05:32.780 に答える
5

はい、理由があります。値を再割り当てしたい場合。その点で、値型と参照型に違いはありません。

次の例を参照してください。

class A
{
    public int B {get;set;}
}

void ReassignA(A a)
{
  Console.WriteLine(a.B);
  a = new A {B = 2};
  Console.WriteLine(a.B);
}

// ...
A a = new A { B = 1 };
ReassignA(a);
Console.WriteLine(a.B);

これは出力されます:

1
2
1

ただし、パフォーマンスはそれとは何の関係もありません。これは本当のマイクロ最適化です。

于 2011-08-24T07:29:23.563 に答える
-1

参照型を値で渡すと、オブジェクトはコピーされません。既存のオブジェクトへの新しい参照を作成するだけです。したがって、本当に必要でない限り、参照渡ししないでください。

于 2011-08-24T07:33:51.430 に答える