메서드 매개 변수(C# 참조)

C#에서는 인수를 값 또는 참조로 매개 변수에 전달할 수 있습니다. C# 형식은 참조 형식() 또는 값 형식(classstruct)일 수 있습니다.

  • 값으로 전달 한다는 것은 변수의 복사본을 메서드에 전달하는 것을 의미합니다.
  • 참조로 전달 한다는 것은 변수에 대한 액세스 권한을 메서드에 전달하는 것을 의미합니다.
  • 참조 형식의 변수에는 해당 데이터에 대한 참조가 포함됩니다.
  • 값 형식의 변수에는 해당 데이터가 직접 포함됩니다.

구조체는 값 형식(C# 참조)이므로 메서드에 구조체를 값으로 전달하는 경우 메서드가 구조체 인수의 복사본을 받아서 작동합니다. 메서드가 호출 메서드의 원래 구조체에 액세스할 수 없으므로 어떤 방식으로든 변경할 수 없습니다. 메서드는 복사본만 변경할 수 있습니다.

클래스 인스턴스는 값 형식이 아니라 참조 형식입니다. 메서드에 참조 형식을 값으로 전달하는 경우 메서드가 클래스 인스턴스에 대한 참조의 복사본을 받습니다. 즉, 호출된 메서드는 인스턴스 주소의 복사본을 수신하고, 호출 메서드는 인스턴스의 원래 주소를 보유합니다. 호출 메서드의 클래스 인스턴스에 주소가 있고, 호출된 메서드의 매개 변수에 주소의 복사본이 있으며, 두 주소가 모두 동일한 개체를 참조합니다. 매개 변수에 주소의 복사본만 포함되므로 호출된 메서드는 호출 메서드의 클래스 인스턴스 주소를 변경할 수 없습니다. 그러나 호출된 메서드는 주소의 복사본을 사용하여 원래 주소와 주소의 복사본이 모두 참조하는 클래스 멤버에 액세스할 수 있습니다. 호출된 메서드가 클래스 멤버를 변경하는 경우 호출 메서드의 원래 클래스 인스턴스도 변경됩니다.

다음 예제의 출력에서 차이점을 보여 줍니다. ClassTaker 메서드가 매개 변수의 주소를 사용하여 클래스 인스턴스의 지정된 필드를 찾기 때문에 클래스 인스턴스의 willIChange 필드 값은 해당 메서드 호출에 의해 변경됩니다. 인수 값이 해당 주소의 복사본이 아니라 구조체 자체의 복사본이기 때문에 호출 메서드의 구조체 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 5를 데이터로 포함합니다. SquareIt을 호출하면 n의 내용이 x 매개 변수로 복사됩니다. 그러면 메서드 내에서 이 매개 변수의 값을 제곱합니다. 그러나 Main에서는 n의 값이 SquareIt 메서드를 호출한 후에도 호출 전과 동일합니다. 즉, 메서드 내에서 수행되는 변경은 지역 변수 x에만 적용됩니다.

참조로 값 형식 전달

참조 형식을 전달하는 경우:

  • 메서드가 매개 변수를 할당하여 다른 개체를 참조하는 경우 이러한 변경 내용은 호출자에서 표시되지 않습니다 .
  • 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하는 경우 해당 변경 내용은 호출자에서 표시되지 않습니다 .

다음 예제는 인수가 ref 매개 변수로 전달된다는 점을 제외하면 이전 예제와 동일합니다. 메서드에서 x가 변경되면 기본 인수 n의 값도 변경됩니다.

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에 대한 참조가 전달됩니다. x 매개 변수는 int가 아니며 int에 대한 참조(이 예제에서는 n에 대한 참조)입니다. 그러므로 메서드 내에서 x를 제곱할 때 실제로는 x가 참조하는 n을 제곱하게 됩니다.

값으로 참조 형식 전달

값으로참조 형식을 전달하는 경우:

  • 메서드가 매개 변수를 할당하여 다른 개체를 참조하는 경우 이러한 변경 내용은 호출자에서 표시되지 않습니다 .
  • 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하면 해당 변경 내용 호출자에서 표시됩니다.

다음 예제에서는 참조 형식 매개 변수 arrChange 메서드에 값으로 전달하는 방법을 보여 줍니다. 매개 변수가 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
*/

앞의 예제에서 참조 형식인 arr 배열은 ref 매개 변수 없이 메서드에 전달됩니다. 이 경우 arr을 가리키는 참조의 복사본이 메서드에 전달됩니다. 출력에서는 이 경우 메서드가 배열 요소의 내용을 1에서 888로 변경할 수 있음을 보여 줍니다. 그러나 Change 메서드 내에서 new 연산자를 사용하여 새 메모리 부분을 할당하면 pArray 변수가 새 배열을 참조합니다. 따라서 그 후의 변경 내용은 Main 내에서 생성된 원래 배열 arr에 영향을 주지 않습니다. 실제로 이 예제에서는 Main 내부와 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 두 가지 범위를 사용합니다.

  • safe_to_escape 범위는 식에 안전하게 액세스할 수 있는 범위를 정의합니다.
  • ref_safe_to_escape 범위는 식에 대한 참조를 안전하게 액세스하거나 수정할 수 있는 범위를 정의합니다.

비공식적으로 이러한 범위를 코드가 더 이상 유효하지 않은 참조에 액세스하거나 수정하지 않도록 하는 메커니즘으로 생각할 수 있습니다. 참조는 유효한 개체 또는 구조체를 참조하는 한 유효합니다. safe_to_escape 범위는 변수를 할당하거나 다시 할당할 수 있는 시기를 정의합니다. ref_safe_to_escape 범위는 변수가 다시 할당되거나 다시 할당될 수 있는 시기를 정의합니다. 할당은 변수를 새 값에 할당합니다. ref 할당 은 다른 스토리지 위치를 참조할 변수를 할당합니다.

한정자

in, ref 또는 out 없이 메서드에 대해 선언된 매개 변수는 값으로 호출된 메서드에 전달됩니다. ref, inout 한정자는 할당 규칙에서 다릅니다.

  • 매개 변수에 대한 인수는 ref 반드시 할당되어야 합니다. 호출된 메서드는 해당 매개 변수를 다시 할당할 수 있습니다.
  • 매개 변수에 대한 인수를 in 반드시 할당해야 합니다. 호출된 메서드는 해당 매개 변수를 다시 할당할 수 없습니다.
  • 매개 변수에 대한 인수는 out 확실히 할당할 필요가 없습니다. 호출된 메서드는 매개 변수를 할당해야 합니다.

이 섹션에서는 메서드 매개 변수를 선언할 때 사용할 수 있는 키워드를 설명합니다.

  • params는 이 매개 변수가 가변 개수의 인수를 사용할 수 있음을 지정합니다.
  • in은 이 매개 변수를 참조로 전달할 수 있지만 호출된 메서드로만 읽을 수 있음을 지정합니다.
  • ref는 이 매개 변수를 참조로 전달할 수 있고 호출된 메서드로 읽거나 쓸 수 있음을 지정합니다.
  • out는 이 매개 변수가 참조로 전달되고 호출된 메서드에 의해 기록되도록 지정합니다.

참고 항목