선택적 매개변수는 메서드 선언에 기본값이 있어서 호출하는 쪽이 생략할 수 있는 매개변수입니다. 명명된 인수는 위치 대신 매개변수 이름으로 값을 넘깁니다. 둘을 함께 쓰면 오버로드를 잔뜩 만들지 않고도 메서드 하나가 여러 호출 형태를 처리할 수 있습니다.
출력:
to ana@mail.com: (no subject), retries 3
to ben@mail.com: Invoice #1042, retries 3
to cy@mail.com: (no subject) [URGENT], retries 3
to dev@mail.com: Build failed, retries 0
세 번째 호출은 subject를 건너뛰고 urgent를 이름으로 설정합니다. 명명된 인수가 없다면 세 번째 위치에 닿기 위해서만 "(no subject)"를 다시 넘겨야 했을 것입니다. 마지막 호출은 모든 인수를 이름으로, 선언과 다른 순서로 넘기며, 이것도 올바른 코드입니다.
기본값 규칙
기본값은 컴파일러가 계산할 수 있는 것이어야 합니다:
- 상수(
3,"INFO",true,1.5m,const필드, 열거형 멤버) - 참조 형식이나 nullable 형식에 대한
null default(T), 또는 값 형식T에 대한new T()
실행 시점에 평가되는 것은 모두 거부됩니다. 전형적인 예가 날짜입니다:
static void Schedule(string task, DateTime at = DateTime.Now) { }
// error CS1736: Default parameter value for 'at' must be a compile-time constant
표준적인 우회 방법은 기본값이 null인 nullable 매개변수를 두고 본문에서 값을 정하는 것입니다:
출력:
backup at 2026-01-01 09:00, tags: 0
report at 2026-03-15 18:30, tags: 0
deploy at 2026-01-01 09:00, tags: 2
같은 방법이 컬렉션에도 통합니다. new List<string>() 기본값은 상수가 아니므로 null을 기본값으로 하고 안에서 리스트를 만드세요. 이렇게 하면 Python식의 공유되는 변경 가능 기본값 버그도 피할 수 있는데, C#은 상수만 허용하므로 애초에 이 버그가 생기지 않습니다.
순서 규칙
필수 매개변수가 먼저, 선택적 매개변수가 그 뒤, params 배열(있다면)이 마지막입니다:
static void Log(string message, string level = "INFO", params string[] tags) { } // OK
static void Log(string level = "INFO", string message) { }
// error CS1737: Optional parameters must appear after all required parameters
ref와 out 매개변수는 선택적일 수 없습니다.
호출하는 쪽에서는 위치 인수가 처음부터 매개변수를 채웁니다. 명명된 인수는 그 뒤에 어떤 순서로든 올 수 있습니다. C# 7.2부터는 명명된 인수가 자기 위치에 있을 때에 한해 위치 인수보다 앞에 올 수도 있습니다(SendEmail("a@b.c", subject: "Hi", true)). 이전 버전에서는 명명된 인수가 모두 마지막에 와야 합니다. 다른 인수에 이름을 붙였더라도 필수 매개변수를 빠뜨리면 컴파일 오류입니다.
가독성을 위한 명명된 인수
아무것도 선택적이지 않아도 명명된 인수는 유용합니다. 리터럴 true, false, null과 맨숫자는 호출 지점에서 아무것도 말해 주지 않습니다:
ResizeImage(photo, 800, 600, true, false); // which is which?
ResizeImage(photo, width: 800, height: 600, keepAspect: true, upscale: false);
매개변수 이름을 바꾸면 이름을 쓰는 호출 코드에는 호환성이 깨지는 변경이 되므로, 공개 라이브러리에서는 기억해 둘 만합니다.
선택적 매개변수와 오버로드
C# 4 이전에는 같은 유연성을 위해 조합마다 오버로드가 필요했습니다. 선택적 매개변수는 이를 메서드 하나로 합칩니다:
// overloads
static void Connect(string host) => Connect(host, 443);
static void Connect(string host, int port) => Connect(host, port, 30);
static void Connect(string host, int port, int timeoutSeconds) { /* ... */ }
// one method with optional parameters
static void Connect(string host, int port = 443, int timeoutSeconds = 30) { /* ... */ }
둘 다 있을 때 오버로드 확인은 기본값을 채울 필요가 없는 후보를 선호합니다:
출력:
Greet()
Greet(string) with Lena
Greet()는 두 메서드 모두와 일치하며, 컴파일러는 생략된 선택적 매개변수가 없는 쪽을 고릅니다. 같은 이름에 두 기법을 섞으면 대체로 어느 메서드가 호출될지 예측하기 어려운 호출이 생기므로, 메서드마다 하나를 고르세요.
변형마다 다른 코드나 다른 매개변수 타입이 필요하면 오버로드를, 기본값만 다르면 선택적 매개변수를 고르세요.
기본값은 호출하는 쪽에 새겨집니다
기본값은 실행 시점에 조회되지 않습니다. 컴파일러가 호출 코드를 컴파일할 때 각 호출 지점에 값을 복사합니다. Connect("api.shop.com")은 Connect("api.shop.com", 443, 30)으로 컴파일됩니다.
이는 라이브러리에 영향을 줍니다. 패키지 버전 1이 Connect(string host, int timeoutSeconds = 30)을 내보냈고, 버전 2가 기본값을 10으로 바꿨다고 해 봅시다. 버전 1로 컴파일된 애플리케이션은 버전 2 DLL로 교체한 뒤에도 애플리케이션 자체를 다시 컴파일할 때까지 계속 30을 넘깁니다. 기존 public 메서드에 새 선택적 매개변수를 추가해도 이미 컴파일된 호출 코드가 깨집니다. 메서드 시그니처가 바뀌었는데 호출 코드는 여전히 예전 시그니처를 찾기 때문입니다(실행 시점에 MissingMethodException).
통째로 컴파일되는 하나의 애플리케이션 안에서는 전혀 문제가 되지 않습니다. NuGet 패키지의 공개 API라면 오버로드(기본값을 라이브러리 안에 유지)나 본문에서 값을 정하는 null 기본값으로 문제를 피할 수 있습니다.
기본값과 선언된 타입
같은 컴파일 시점 규칙 때문에, 인터페이스와 클래스가 모두 기본값을 선언하면 기본값은 객체가 아니라 호출에 쓴 변수의 타입에서 옵니다:
출력:
printing "report" x5
printing "report" x1
같은 객체인데 기본값이 두 가지입니다. 인터페이스와 그 구현의 기본값을 똑같이 유지하거나, 한 곳에만 선언하세요.
호출자 정보 특성
선택적 매개변수는 System.Runtime.CompilerServices의 호출자 정보 특성도 가능하게 합니다. 컴파일러가 호출 지점의 정보로 값을 채웁니다:
출력:
[Main:18] starting
[SaveOrder:13] order saved
로깅 라이브러리가 호출하는 쪽이 직접 입력하지 않아도 메시지가 어디서 왔는지 기록하는 방법이며, INotifyPropertyChanged 구현이 속성 이름을 얻는 방법입니다. [CallerFilePath]는 같은 방식으로 소스 파일 경로를 추가합니다.
자주 묻는 질문
C#에서 매개변수를 선택적으로 만들려면 어떻게 하나요?
선언에서 기본값을 주세요: static void Log(string message, string level = "INFO"). 그러면 호출하는 쪽은 Log("started")나 Log("failed", "ERROR")라고 쓸 수 있습니다. 선택적 매개변수는 모든 필수 매개변수 뒤에 와야 하고, 기본값은 컴파일 시점 상수여야 합니다.
C#의 명명된 인수란 무엇인가요?
매개변수 이름과 함께 넘기는 인수입니다: SendEmail(to: "ana@mail.com", urgent: true). 중간의 선택적 매개변수를 건너뛰고, 인수를 어떤 순서로든 넘기며, true/false나 숫자 리터럴을 쓰는 호출을 한눈에 읽을 수 있게 해 줍니다.
DateTime.Now를 매개변수 기본값으로 쓸 수 없는 이유는 무엇인가요?
기본값은 컴파일 시점 상수여야 하는데 DateTime.Now는 실행 시점에 계산되므로, 컴파일러가 CS1736 Default parameter value for 'at' must be a compile-time constant를 보고합니다. 대신 nullable 매개변수를 쓰세요: DateTime? at = null로 선언하고 본문에서 DateTime time = at ?? DateTime.Now;라고 씁니다.
C#에서 선택적 매개변수와 메서드 오버로드 중 무엇을 써야 하나요?
변형들이 기본값만 다르다면 선택적 매개변수가 더 간단합니다. 변형마다 다른 로직이나 타입이 필요할 때, 그리고 공개 라이브러리에서는 오버로드가 낫습니다. 기본값은 모든 호출 코드에 컴파일되어 들어가므로 나중에 바꿔도 이미 컴파일된 코드에는 반영되지 않기 때문입니다.
"Optional parameters must appear after all required parameters"는 무슨 뜻인가요?
CS1737 오류입니다. 기본값이 있는 매개변수 뒤에 기본값이 없는 매개변수가 온다는 뜻입니다. 필수 매개변수를 앞으로 옮기세요: (bool urgent = false, string to)가 아니라 (string to, bool urgent = false). 선택적 매개변수 뒤에는 다른 선택적 매개변수나 params 배열만 올 수 있습니다.