본문 바로가기

프로그래밍/C#

CLR via C# 9장 매개변수

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

 

    메서드를 선언할 때, 매개변수에 기본값(default)을 설정할 수 있다.

    이렇게 매개 변수에 기본값을 설정한 매서드를 호출할 때에도 매개변수를 선택적으로 사용할 수 있다. 이 떄 이 선택에 누락된 매개변수는 기본값이 대입된다.

    메서드를 호출할 때 특정 매개변수의 이름을 지정하여 호출하는 것도 가능하다.

 

 


매개 변수에 기본값을 지정할 때의 규칙과 가이드라인.

  • 메서드, 생성자 메서드, 인덱서 (this[int idx] 로 접근하는 메서드)와 같이 매개변수가 존재하는 프로퍼티, 델리게이트를 사용할 경우 기본값을 지정할 수 있다.
  • 기본값이 있는 매개변수는 C++과 동일하게, 기본값이 없는 매개변수보다 앞에 와야 한다.
  • 기본값은 컴파일 타임에 알 수 있는 값이어야 한다. 레퍼런스 타입은 null만이 가능하며, 값 타입은 모든 필드가 0으로 채워진 기본 값을 설정할 수 있다. default(T)혹은 new T()로 사용하면 되고, 동일한 IL 코드를 만든다.
  • 메서드를 정의하는 쪽에서 이름을 바꾸는 순간 사용하는 쪽에서 컴파일 오류가 날 수 있으므로 지정한 매개변수의 이름이 바뀌지 않도록 주의.
  • 모듈의 외부에서 불리는 함수의 경우 기본값을 지정할 때 null, 0등의 값으로 지정하자. 함수가 변경될 일이 있을 경우, 모듈 외부가 컴파일 되지 않는 한 기본값이 그대로 들어온다.

 

  • ref, out 키워드의 매개변수에는 사용할 수 없다.

 

 C#의 경우 기본값을 작성할 경우 컴파일러가 내부적으로 OptionalAttribute 특성과 DefaultParameterValue 특성을 해당 매개변수에 붙여주고, 파일 메타데이터에 기록한다. 

 


암시적으로 타입화된 지역 변수

    C#에서는 var를 사용하여 타입의 초기화 표현식으로부터 타입을 유추할 수 있는 기능을 제공하고 있다.

 

 

  • 매개변수나 멤버 필드 타입으로는 var를 사용할 수 없다.

 


매서드에 참조로 매개변수 전달

 CLR에서는 모든 메서드의 매개변수를 값으로 전달하는 것을 전제로 하고 있다.

 레퍼런스 타입의 객체가 매개변수로 전달되는 경우 C++과 마찬가지로 객체의 레퍼런스(또는 포인터)를 값으로 매서드에 전달한다. 

 밸류 타입의 객체가 전달되는 경우 객체가 복사되어 전달되고, 어떠한 일을 하더라도 원본 밸류 타입 객체에는 영향을 주지 못한다.

 

 CLR은 값으로 매개변수를 전달하는 것 외에, 참조로 매개변수를 전달할 수 있다. C#은 이를 위해 out / ref 키워드를 만들어두었다.

 해당 키워드는 모두 C# 컴파일러가 매개변수에 대하여 참조를 전달받도록 메타데이터를 생성하게 하며, 컴파일러는 매개변수 그 자체가 아니라 매개변수의 주소 값을 이용하는 코드를 생성한다.

 

 CLR의 관점에서는 out / ref 키워드가 동일한 의미를 지니고, 동일한 IL 코드를 만들어낸다.

 메타데이터의 경우에도 out, ref 키워드를 기록하는 1비트를 제외하고는 같은 값을 가진다.

 그러나 C# 컴파일러의 입장에서는 두 키워드를 엄격하게 다른 키워드로 생각하며, 그 차이는 초기화의 의무에 있다.

 

 out 키워드는 매개변수의 초기화를 메서드에서 책임진다는 것을 의미한다.

 ref 키워드는 전달된 매개변수가 이미 초기화되었다는 것을 전제로, 매개 변수의 초기 값을 읽을 수 있다.

 나머지는 두 키워드가 동일하게 인스턴스의 포인터를 전달하는 기능을 수행한다.

 

 추가적으로 out, ref 키워드 또한 메서드의 일부로 파악하고 이를 기반으로 하는 오버로드를 허용한다. 다만, out / ref 간에는 오버로드를 지원하지 않는다. 메타 데이터의 관점에서는 전혀 차이가 없기 때문이라고 한다.

 

 

 


매서드에 가변 매개변수 전달하기

 params 키워드를 이용하여 가변 매개 변수를 받는 메서드를 정의할 수 있다.

 

 

 params 키워드가 지정된 매개변수의 경우, 기본값이 지정된 매개변수들 보다도 뒤에 위치해야 한다.

 C# 컴파일러는 System.ParamArrayAttirbutes 특성을 해당 매개변수에 붙인다.

 C# 컴파일러가 만일 이러한 메서드를 수행하도록 하는 코드를 만나면, 우선 같은 이름의 가변 매개변수를 사용하지 않는 메서드가 있는지 확인한다. 그래서 타입이 맞는 다른 함수가 있다면 그 함수로 처리할 수 있도록 처리한다.

 하지만 일치하는 메서드가 없다면 ParamArray 특성이 있는 메서드들을 찾고 호출 규칙에 부합하는지 확인한다. 만약 호출 규칙에 맞는 다면 배열을 만들고 지정된 모든 요소들을 배열에 추가한 후, 이 배열을 매개 변수로 전달하도록 한다.

 params 타입이 지정된 매개변수는 반드시 1차원 배열이여야 하고, null이나 비어있는 배열을 전달할 수 있다.

 

 

 만약 Object[] 타입을 가변 매개 변수로 전달한다면 모든 형식의 호출을 커버 할 수 있는 경우의 메서드가 될 것이다.

 

 가변 매개변수를 전달하는 경우에는 null을 전달하지 않는 이상 추가적인 성능 저하가 있을 수 있다.

 결국 힙에 배열 객체가 반드시 할당 되고, 배열의 각 요소들이 초기화 되며, 배열의 메모리들도 가비지 컬렉션의 대상이 될 것이기 때문이다.

 따라서 자주 사용될 가능성이 있는 패턴들은 미리 오버로드의 형태로 구현해주는 것이 좋다. (String.Concat이 대표적)

 


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

 메서드의 매개변수 타입을 정의할 때에는 가장 상위의 추상화 된 타입이나 인터페이스 타입을 사용하는 것이 좋다.

 예를 들어 List<T>, ICollection<T>, IList<T> 보다는 IEnumerable<T>가 좋을 것이고, FileStream보다는 Stream형식을 택하는 것이 좋을 것이다.

 

 반환 타입은 가능한 구체적인 타입으로 정의하는 것이 좋다. Stream타입 보다는 FileStream 타입을 반환하는 것이 함수의 의도를 분명히 표현할 수 있고, 호출하는 쪽에서 가능한 최대한 유연하게 사용할 수 있을 것이다.

 

 그러나 호출하는 측의 코드에 영향을 주지 않으면서도 메서드의 내부 구현을 변경해야할 일이 있을 것이다. 이러한 상황에서도 유연성을 유지하려면 메서드의 반환 타입을 조금 더 추상화된 타입으로 정의하는 것이 좋다.

 


상수화

 C++ 언어와 같은 비관리 언어의 경우, 메서드나 매개변수를 const로 선언하여 상수성을 표현할 수 있다.

 그러나 CLR이 이러한 기능을 제공하지 않기 때문에 C#을 포함한 어떤 언어도 이 기능을 제공하지 않는다.

 

 왜 CLR이 이런 기능을 지원하지 않을까?

 

  • C++과 같은 언어들도 완벽한 상수성을 지원하지 않음. const_cast라던가 주소 값을 얻어와서 변경한다던가... 코드 적으로 실수하는 부분을 막을 뿐이다.
  • MS가 상수화된 객체가 매개변수가 변경되지 않았음을 검증하는 기능을 CLR에 추가하기도 매우 어려울 것이다. 이러한 기능을 지원하려면 CLR이 상수화된 객체에 대해 Write 작업이 발생하지 않았는지 매번 체크해야 할 것이고, 이는 성능 저하를 발생시킬 것이다.
  • 개발자들에게 수 많은 복잡성을 증대시킨다. 상수성이 있는 타입이라면 이를 상속하는 나머지 모든 타입들 역시 이 특성을 준수해야하고, 나아가서 타입이 가지는 필드들 또한 모두 상수성이 있도록 개발해야되기 때문.

위와 같은 이유 때문에 상수화를 지원하지 않는다고 한다.