본문 바로가기
C#

[C#] ref, in, out, params

by ifhead 2022. 9. 10.
반응형

매개변수 한정자(ref, in, out, params)란?

 

매개변수 한정자는 메소드가 받는 인자가 어떤 형태여야 하는지, 어떻게 취급되어야 하는지 명시하는 키워드입니다. 기능과 용도가 달라서  제대로 이해하려면 하나씩 짚어보아야 합니다.

 


 

ref 매개변수 한정자

 

변수의 주소를 복사해서 할당과 메소드를 통해 원래 값을 조작할 수 있습니다. 값 형식은 원래 이러한 방식으로 조작할 수 없었다는 사실을 기억합시다. ref 키워드를 달아서 변수의 주소를 복사해보면 다음과 같은 결과를 얻을 수 있습니다.

 

할당을 통해 변수를 조작

using System;

public class Program {
  public static void Main() {
    int a = 0;

    Console.WriteLine(a);

    ref int aa = ref a;
    aa = 1;

    Console.WriteLine(a);
  }
}

// 결과 :
// 0
// 1

 

메소드를 통해 변수를 조작

using System;

public class Program {
  public static void Main() {
    int b = 0;

    Console.WriteLine(b);

    foo(ref b);

    Console.WriteLine(b);
  }

  public static void foo(ref int num) { num = 1; }
}

// 결과 :
// 0 
// 1

 

ref 키워드를 사용한 변수는 초기화를 생략할 수 없습니다. 아무것도 가리키지 않는 변수가 메소드로 들어가면 에러가 납니다.

 


 

in 매개변수 한정자

 

첫 번째로, in 키워드는 readonly 성질을 보장하는 용도로 쓸 수 있습니다.

  • in 키워드는 매개 변수를 항상 참조로 받지만, 그 매개변수는 메소드 내에서 수정될 수 없습니다.
  • in으로 값 타입(int, float 등)을 받으면 메소드 내에서 수정이 불가능합니다.
int readonlyArgument = 0;
InArgExample(readonlyArgument); // 여기에서 in readonlyArgument처럼 in을 붙이지 않습니다.
Console.WriteLine(readonlyArgument);     // value is still 44

void InArgExample(in int number)
{
    // 주석을 풀면 CS8331 에러가 나옵니다.
    //number = 1;
}

 

두 번째로, in 키워드는 재할당을 막는 용도로 쓰입니다.

  • 메소드로 들어오기 전에, 인수는 반드시 초기화되어 있어야 합니다.
  • 매개변수는 메소드 내에서 다른 인스턴스로 재할당될 수 없습니다.
  • in으로 구조체, 클래스 등의 참조 타입을 받았다면 수정이 가능합니다.
using System;

class ReferenceTypeExample
{
  static void Enroll(in Student student)
  {
    // 새로운 클래스를 할당하려고 하면 에러가 발생합니다.
    // student = new Student();

    // 매개변수가 레퍼런스 타입이면 in이라도 원본을 수정할 수 있습니다.
    student.Enrolled = true;
  }

  static void Main()
  {
    var student = new Student
    {
      Name = "Susan",
      Enrolled = false
    };

    Enroll(student);
    System.Console.WriteLine(student.Enrolled);
  }
}

public class Student
{
  public string Name { get; set; }
  public bool Enrolled { get; set; }
}

// 결과 :
// True

 

성능 테스트 결과 - 게임 프로그래머 Rito15님의 포스팅

in 매개변수 한정자는 C# 7.2에서 도입되어 성능상의 이유로 가장 자주 사용됩니다. in을 사용해야하는 가장 큰 이유는 '수정되지 않음'을 선언하여 구조체의 성능을 향상시키는 데 있습니다. - Pluralsight.com Matt Ferderer
struct, readonly struct, int, float, double, string 타입의 매개변수를 전달받는 메소드를 각각 매개변수 한정자 없이, in 매개변수 한정자 사용, ref 매개변수 한정자를 사용하는 형태로 만들어 반복 호출하며 시간을 측정한다.

게임 프로그래머 Rito 15님 테스트 감사합니다.

 

  • 결론 : Value Type 사용 시에는 in을 써도 성능 향상이 없습니다. Struct는 in, ref를 붙이는 편이 훨씬 빠릅니다.

 


 

out 매개변수 한정자

out 매개변수가 있는 메소드는 주로 null 값을 받아와 초기화해주는 역할을 합니다.

  • out을 이용하면 변수를 메소드 안에서 초기화합니다.
  • ref와 마찬가지로 메소드 안에서 일어나는 모든 수정은 바깥의 변수에도 반영됩니다.
  • ref와 다르게 out으로 받아왔다면 반드시 수정해야 합니다.
  • 그 외의 사용법은 ref와 유사합니다.
class ReferenceTypeExample
{
  static void Enroll(out Student student)
  {
    //우선적으로 초기화부터 해줘야 합니다.
    student = new Student();
    student.Enrolled = true;
  }

  static void Main()
  {
    Student student;
    Enroll(out student); // student.Enrolled는 true로 초기화됩니다.
  }
}

public class Student {
  public string Name {get;set;}
  public bool Enrolled {get;set;}
}

 

out은 아래 예시처럼 값 타입도 받습니다. 

int x;
Int32.TryParse("3", out x);

 


 

Params 매개변수 한정자

in 키워드는 여러 개의 변수를 매개변수로 받을 때 사용합니다.

  • 단, 메소드에서 params 매개변수만 사용할 수 있습니다.
  • 매개변수의 타입은 반드시 1차원 배열이어야 합니다.
  • 배열 인스턴스를 인수로 사용할 수 있습니다.
  • 호출 시에 foo(1, 2, 3, 4); 처럼 콤마로 나눠진 형태로 배열을 전달할 수 있습니다.
  • foo(); 처럼 인수가 없다면 params는 길이가 0인 배열이 됩니다.
public class MyClass
{
    public static void UseParams(params int[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    public static void UseParams2(params object[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        // 콤마로 나눠진 배열을 전달할 수 있습니다.
        UseParams(1, 2, 3, 4);
        UseParams2(1, 'a', "test");

        // 인수가 없어도 됩니다.
        UseParams2();

        // 호출된 타입만 맞다면 배열을 전달할 수 있습니다.
        int[] myIntArray = { 5, 6, 7, 8, 9 };
        UseParams(myIntArray);

        object[] myObjArray = { 2, 'b', "test", "again" };
        UseParams2(myObjArray);

        // 이것은 타입이 맞지 않으니 에러가 납니다.
        //UseParams(myObjArray);

        // 이것은 에러를 일으키지는 않지만 int 배열이 
        // 통째로 obj 배열의 첫 번째 인덱스에 몰립니다.
        UseParams2(myIntArray);
    }
}

/*
결과:
    1 2 3 4
    1 a test

    5 6 7 8 9
    2 b test again
    System.Int32[]
*/

 


 

요약

 

 

params는 매개변수로 배열을 받기 위한 용도라서 표로 비교하지 않았습니다.

 

반응형

댓글