언어/C#

C# 매개변수

tsyang 2020. 12. 13. 16:02

선택된 매개변수와 명명된 매개변수


 

메서드의 매개변수를 정할 때 일부 또는 전체에 대해 기본값을 정할 수 있다.

 

    public static void M(int a = 1, int b = 2, int c = 3) { }
    public static void Main()
    {
        M(); //M(1,2,3)
        M(1, c: 2, b: 1); // 콜론(:) 을 이용해서 값을 전할 매개변수를 고를 수 있다.
    }

호출 시점에 매개변수를 생략하면 ,C# 컴파일러는 매개변수의 기본값을 포함하는 코드를 생성한다. 

 

모듈 외부에서 불리는 메서드의 경우 기본값을 바꾸는건 위험할 수 있는데, 만약 매개변수의 기본값을 바꾼 경우 호출하는 쪽의 코드도 다시 컴파일 하지 않으면 예전의 기본값을 가진 메서드를 호출하기 때문이다. 

 

따라서

    public string GetPath(string fileName = "Untitled")
    {
        return String.Format(@"C:\{0}.txt", fileName);
    }

    public string GetPath2(string fileName = null)
    {
        return String.Format(@"C:\{0}.txt", fileName ?? "Untitled"); // 널 결합 연산자, 널이면 ?? 뒤의 값 반환
    }

 

GetPath 보다는 GetPath2 처럼 쓰는게 더 관리하기 편하다.

 

 

 

참조로 매개변수 전달 (out, ref)


 

out, ref 키워드는 참조를 넘긴다. out은 변수를 초기화하는데 쓸 수 있다는 점에서 다른데,

 

    public void RefFunc(ref Object obj) { }
    public void OutFunc(out Object obj)
    {
        obj = new object(); //obj 초기화 없으면 컴파일 에러
    } 

    public void Func()
    {
        Object a;
        Object b;

        RefFunc(ref a); // 에러 : 할당되지 않은 값 a 를 사용함
        OutFunc(out b);
    }

 

CLR에서는 이 둘의 키워드는 동일한 의미이다. 메타데이터 같은 경우에도 ref인지 out인지 구분하는 1비트를 제외하고 같은 값을 가지며 같은 IL코드를 만든다.

 

즉, IL 혹은 CLR의 관점에서 이 둘은 동일하게 인스턴스의 포인터를 전달한다는 기능을 갖는다. 유일한 차이점은 키워드가 의미하는 바를 문법적으로 확인하는 정도다. 

 

ref 와 out은 오버로드에도 사용할 수 있는데,

 

    public void Add(int a) { }

    // 둘 중 하나만 오버로드 할 수 있다.
    public void Add(ref int a) { }
    public void Add(out int a) { a = 1; }

ref와 out은 CLR관점에서 같은 것이므로 둘 다를 오버로드 할 수는 없다. 

 

 

가변 매개변수


 

C#에서는 가변 매개변수를 전달할 수 있다.

 

    public static int Sum(params int[] values)
    {
        int sum = 0;

        if (values != null)
            for (int i = 0; i < values.Length; ++i)
                sum += values[i];

        return sum;
    }

    public static void Main()
    {
        Sum(1, 2, 3);
        Sum(); // null 이 아니라 new int[0] 이 전달됨
        Sum(null);
    }

 

컴파일러는 가변 매개변수를 받으면 내부적으로 배열을 생성하고 모든 매개변수를 배열에 추가한 뒤, 이 배열을 최종 매개변수로 전달하는 일을 대신 수행한다.

 

그러나 위와같은 과정 때문에 가변 매개변수에 null이 아닌 값을 넣는 경우 성능 저하가 있을 수 있다. 왜냐면 결국 힙에 배열 객체를 할당하고, 요소들을 초기화해야 하며 종국에는 배열의 메모리도 가비지 컬렉션의 대상이 되기 때문이다. 그래서 자주쓰이는 패턴들은 미리 오버로드 형태로 구현해주는게 좋다. 

 

그 예로 System.String 클래스의 Concat 메서드는 다음과 같이 정의되어 있다.

public static String Concat(String str0, String str1);
public static String Concat(String str0, String str1, String str2);
public static String Concat(String str0, String str1, String str2, String str3);
public static String Concat(params String[] values);

 

 

 

 

 

기타


 

매개변수 타입과 반환에 대한 지침.

 

메서드의 매개변수 타입을 정의할 때는 가능한 가장 추상화된 타입(상속에 있어서 가장 상위의 타입) 이나 인터페이스를 사용하는게 좋다. 당연한 소리지만 재사용할 수 있도록 만든 메서드가 좋다는 의미.

 

예를들어 List<T>를 매개변수로 사용하는 경우에도 정말 리스트가 필요한게 아니라면 IList<T>를 사용하는게 낫다. 

 

반면에 반환 타입의 경우 가능한 구체적인게 의미가 더 분명하다.

 


 

상수화

 

C++ 같은 비관리 언어의 경우 메서드에 객체의 필드를 변경하지 못하는 상수화 키워드를 제공한다. (const)

 

그러나 이런건 실제로 매개변수를 다시 역으로 캐스팅하거나 실제 주소를 얻어와서 알마든지 객체나 매개변수의 값과 상태를 자유롭게 변경할 수 있다. 따라서 C++의 const키워드가 객체나 매개변수가 절대로 변경되지 않게 한다는 것은 잘못된 사실이다. 

 

만약 CLR에서도 상수화를 지원한다면 상수화된 객체에 대해 쓰기 작업이 발생하지 않았는지 매 순간 검사해야 하며 이로 인해서 성능이 저하될 것이다. 

 

이러한 이유로 CLR은 객체나 매개변수에 상수화를 지원하지 않는다.