선택적 매개변수와 명명된 매개변수
메서드를 선언할 때, 매개변수에 기본값(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 작업이 발생하지 않았는지 매번 체크해야 할 것이고, 이는 성능 저하를 발생시킬 것이다.
- 개발자들에게 수 많은 복잡성을 증대시킨다. 상수성이 있는 타입이라면 이를 상속하는 나머지 모든 타입들 역시 이 특성을 준수해야하고, 나아가서 타입이 가지는 필드들 또한 모두 상수성이 있도록 개발해야되기 때문.
위와 같은 이유 때문에 상수화를 지원하지 않는다고 한다.
'프로그래밍 > C#' 카테고리의 다른 글
C# 9.0이 공개되었습니다. (0) | 2020.05.21 |
---|---|
[NDC] 이놈의 enum의 박싱을 어찌할꼬 (동영상링크) (0) | 2020.01.05 |
CLR via C# 23장 어셈블리 로딩과 리플렉션 (0) | 2019.12.09 |
CLR via C# 2장 빌드, 패키징, 배포, 응용프로그램과 타입의 관리 (0) | 2019.09.24 |
CLR via C# 17장 델리게이트 (0) | 2019.09.18 |