メソッドのパラメーター (C# リファレンス)

C# では、引数を値または参照によってパラメーターに渡すことができます。 C# 型は、参照型 (class) と値の型 (struct) のどちらかになることに注意してください。

  • "値渡し" は、メソッドに変数のコピーを渡すことを意味します。
  • "参照渡し" は、メソッドに変数へのアクセスを渡すことを意味します。
  • "参照型" の変数には、そのデータへの参照が格納されます。
  • "値の型" の変数には、そのデータが直接格納されます。

構造体は値型であるため、メソッドに構造体が値によって渡されると、メソッドは構造体引数のコピーを受け取って操作します。 メソッドは、呼び出し側メソッドの元の構造体にはアクセスできないため、どのような場合でもこの構造体を変更することはできません。 メソッドで変更できるのはコピーのみです。

クラス インスタンスは、値型ではなく、参照型です。 メソッドに参照型が値によって渡されると、メソッドはクラス インスタンスへの参照のコピーを受け取ります。 つまり、呼び出されたメソッドは、インスタンスのアドレスのコピーを受け取り、呼び出し元のメソッドはインスタンスの元のアドレスを保持します。 呼び出し側メソッドのクラス インスタンスにはアドレスがあり、呼び出されたメソッドのパラメータにはそのアドレスのコピーがあり、両方のアドレスが同じオブジェクトを参照します。 パラメーターにはアドレスのコピーのみが含まれるため、呼び出されたメソッドは呼び出し側メソッドのクラス インスタンスのアドレスを変更できません。 ただし、呼び出されたメソッドはアドレスのコピーを使用して、元のアドレスとアドレスのコピーの両方が参照するクラス メンバーにアクセスできます。 呼び出されたメソッドがクラス メンバーを変更すると、呼び出し側メソッドの元のクラス インスタンスも変更されます。

次の例の出力はこの違いを示しています。 クラス インスタンスの willIChange フィールドの値はメソッド ClassTaker の呼び出しによって変更されます。これは、メソッドがパラメーターのアドレスを使用して、クラス インスタンスの指定されたフィールドを検索するためです。 呼び出し側メソッドの構造体の willIChange フィールドはメソッド StructTaker の呼び出しによって変更されません。これは、引数の値が、そのアドレスのコピーではなく、構造体自体のコピーであるためです。 StructTaker はコピーを変更し、そのコピーは、StructTaker の呼び出しが完了したときに失われます。

class TheClass
{
    public string? willIChange;
}

struct TheStruct
{
    public string willIChange;
}

class TestClassAndStruct
{
    static void ClassTaker(TheClass c)
    {
        c.willIChange = "Changed";
    }

    static void StructTaker(TheStruct s)
    {
        s.willIChange = "Changed";
    }

    public static void Main()
    {
        TheClass testClass = new TheClass();
        TheStruct testStruct = new TheStruct();

        testClass.willIChange = "Not Changed";
        testStruct.willIChange = "Not Changed";

        ClassTaker(testClass);
        StructTaker(testStruct);

        Console.WriteLine("Class field = {0}", testClass.willIChange);
        Console.WriteLine("Struct field = {0}", testStruct.willIChange);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Class field = Changed
    Struct field = Not Changed
*/

引数の受け渡し方法、および引数が参照型か値の型かによって、引数に加えられるどの変更が呼び出し元から見えるかが制御されます。

値の型を値渡しで渡す

"値" の型を "値渡し" で渡す場合:

  • メソッドがパラメーターを代入して別のオブジェクトを参照する場合、それらの変更は呼び出し元からは見えません
  • メソッドがパラメーターによって参照されるオブジェクトの状態を変更する場合、それらの変更は呼び出し元からは見えません

次の例では、値型のパラメーターを値渡しで渡す方法について説明します。 変数 n を、値渡しでメソッド SquareIt に渡します。 メソッド内で生じた変更が、変数の元の値に影響することはありません。

int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);

SquareIt(n);  // Passing the variable by value.
System.Console.WriteLine("The value after calling the method: {0}", n);

// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();

static void SquareIt(int x)
// The parameter x is passed by value.
// Changes to x will not affect the original value of x.
{
    x *= x;
    System.Console.WriteLine("The value inside the method: {0}", x);
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 5
*/

変数 n は値型です。 この変数には、そのデータである値 5 が含まれます。 SquareIt を呼び出すと、n の内容がパラメーター x にコピーされ、このパラメーターがメソッド内で 2 乗されます。 ただし、Main 内の n の値は、SquareIt メソッドを呼び出した後でも以前の値と同じままです。 メソッド内で生じた変更の影響は、ローカル変数 x にのみ及びます。

値の型を参照渡しで渡す

"値" の型を "参照渡し" で渡す場合:

  • メソッドがパラメーターを代入して別のオブジェクトを参照する場合、それらの変更は呼び出し元からは見えません
  • メソッドがパラメーターによって参照されるオブジェクトの状態を変更する場合、それらの変更は呼び出し元からは見えません

次の例は、ref パラメーターとして引数を渡す点を除いて上記の例と同じです。 基になる引数 n の値は、メソッド内で x が変更されると変わります。

int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);

SquareIt(ref n);  // Passing the variable by reference.
System.Console.WriteLine("The value after calling the method: {0}", n);

// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();

static void SquareIt(ref int x)
// The parameter x is passed by reference.
// Changes to x will affect the original value of x.
{
    x *= x;
    System.Console.WriteLine("The value inside the method: {0}", x);
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 25
*/

この例では、n の値が渡されるのではなく、n への参照が渡されています。 つまり、パラメーター xint ではなく、int への参照 (この場合は n への参照) となります。 したがって、メソッド内で x が 2 乗された場合、x の参照先である n が実際に2乗されます。

参照型を値渡しで渡す

"参照" 型を "値渡し" で渡す場合:

  • メソッドがパラメーターを代入して別のオブジェクトを参照する場合、それらの変更は呼び出し元からは見えません
  • メソッドがパラメーターによって参照されるオブジェクトの状態を変更する場合、それらの変更は呼び出し元から見えます

次の例では、参照型のパラメーター arr を値渡しで Change メソッドに渡す方法について説明します。 このパラメーターは arr への参照であるため、配列要素の値を変更できます。 ただし、別のメモリ位置へのパラメーターの再割り当ては、メソッドの内部でのみ機能し、元の変数 arr には影響しません。

int[] arr = { 1, 4, 5 };
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

Change(arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);

static void Change(int[] pArray)
{
    pArray[0] = 888;  // This change affects the original element.
    pArray = new int[5] { -3, -1, -2, -3, -4 };   // This change is local.
    System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: 888
*/

前の例では、ref パラメーターなしで参照型の配列 arr がメソッドに渡されています。 このような場合は、arr を指す参照のコピーがメソッドに渡されます。 出力は、メソッドが配列要素のコンテンツを変更できることを示しています。この場合は 1 から 888 に変更します。 ただし、Change メソッド内で new 演算子を使用して新しいメモリ領域を割り当てると、変数 pArray が新しい配列を参照します。 このため、この後のすべての変更が、Main の内部で作成される元の配列 arr に影響しません。 実際、この例では、2 つの配列が作成されます。1 つは Main の内部で、もう 1 つは Change メソッド内で作成されます。

参照型を参照渡しで渡す

"参照" 型を "参照渡し" で渡す場合:

  • メソッドがパラメーターを代入して別のオブジェクトを参照する場合、それらの変更は呼び出し元から見えます
  • メソッドがパラメーターによって参照されるオブジェクトの状態を変更する場合、それらの変更は呼び出し元から見えます

次の例は、前の例と同じですが、ref キーワードがメソッド ヘッダーと呼び出しに追加されている点が異なります。 メソッドで行われるすべての変更が、呼び出し元のプログラム内の元の変数に影響します。

int[] arr = { 1, 4, 5 };
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

Change(ref arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);

static void Change(ref int[] pArray)
{
    // Both of the following changes will affect the original variables:
    pArray[0] = 888;
    pArray = new int[5] { -3, -1, -2, -3, -4 };
    System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: -3
*/

メソッド内で行われるすべての変更は、Main の元の配列に影響します。 実際、元の配列は new 演算子を使用して再割り当てされます。 したがって、Change メソッドを呼び出した後、arr へのすべての参照が、Change メソッドで作成された 5 つの要素を持つ配列を指します。

参照と値のスコープ

メソッドでは、パラメーターの値をフィールドに格納できます。 パラメーターが値渡しで渡される場合は、常に安全です。 値がコピーされ、フィールドに格納された場合に参照型にアクセスできます。 パラメーターを参照渡しで安全に渡すには、コンパイラが新しい変数に参照を安全に代入するタイミングを定義する必要があります。 すべての式について、コンパイラは式または変数へのアクセスを制限する "スコープ" を定義します。 コンパイラでは、safe_to_escaperef_safe_to_escape という 2 つのスコープが使われます。

  • safe_to_escape スコープは、任意の式に安全にアクセスできるスコープを定義します。
  • ref_safe_to_escape スコープは、任意の式への "参照" に安全にアクセスしたり、変更したりできるスコープを定義します

一般にこれらのスコープは、有効ではなくなった参照にコードがアクセスしたり、変更したりしないようにするためのメカニズムと考えることができます。 ある参照は、有効なオブジェクトまたは構造体を参照している限り有効です。 safe_to_escape スコープは、変数を代入または再代入できるタイミングを定義します。 ref_safe_to_escape スコープは、変数を ref 代入または ref 再代入できるタイミングを定義します。 代入では、新しい値に変数を代入します。 "ref 代入" では、変数を代入して別の記憶域の場所を "参照" します。

修飾子

inref または out のないメソッドで宣言されたパラメーターは、呼び出されたメソッドに値で渡されます。 refinout の各修飾子は、代入ルールが異なります。

  • ref パラメーターの引数は、必ず代入する必要があります。 呼び出されたメソッドでは、そのパラメーターを再代入できます。
  • in パラメーターの引数は、必ず代入する必要があります。 呼び出されたメソッドでは、そのパラメーターを再代入できません。
  • out パラメーターの引数は、必ず代入する必要はありません。 呼び出されたメソッドでは、パラメーターを代入する必要があります。

ここでは、メソッドのパラメーターを宣言するときに使用できるキーワードについて説明します。

  • params は、このパラメーターが異なる数の引数を取得する可能性があることを指定します。
  • in は、このパラメーターが参照によって渡されますが、呼び出されたメソッドでは読み取りのみが行われることを指定します。
  • ref は、このパラメーターが参照によって渡され、呼び出されたメソッドでは読み取りまたは書き込みが行われる可能性があることを指定します。
  • out は、このパラメーターが参照によって渡され、呼び出されたメソッドでは書き込みが行われることを指定します。

関連項目