Menu

C# 삼항 연산자(?:): 문법, 타입, 중첩과 ??

C# 조건 연산자 condition ? a : b는 두 값 중 하나를 고릅니다. 평가 방식, 두 분기에 공통 타입이 필요한 이유(CS0173)와 이를 고치는 캐스트, 중첩 삼항 연산자, 문자열 보간 안의 괄호, 그리고 ??와 ?.가 더 잘 표현하는 경우를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

흔히 삼항 연산자라고 부르는 조건 연산자는 하나의 식 안에서 두 값 중 하나를 고릅니다: condition ? valueIfTrue : valueIfFalse.

출력:

0 items, shipping 4.99
1 item, shipping 4.99
5 items, shipping 0

조건은 bool이어야 합니다. 조건을 평가한 뒤 두 분기 중 하나만 실행되고, 다른 하나는 절대 평가되지 않습니다. 그래서 list.Count > 0 ? list[0] : "empty"처럼 빈 리스트에서 list[0]을 평가하면 예외가 나는 보호된 접근에도 안전합니다.

삼항 연산자와 if/else

삼항 연산자는 식입니다. 대입하거나, 넘기거나, 반환하는 값을 만듭니다. if 문은 문장을 실행합니다. 다음 두 메서드는 같은 일을 합니다:

// with if/else
static string Status(int stock)
{
    if (stock > 0)
        return "in stock";
    else
        return "sold out";
}

// with the ternary
static string Status(int stock) => stock > 0 ? "in stock" : "sold out";

두 분기가 같은 종류의 짧은 값일 때 삼항 연산자를 쓰세요. 분기가 여러 문장을 실행하거나, 부수 효과가 있거나, 조건이 길어서 한 줄에 편하게 들어가지 않을 때는 if를 쓰세요.

동작만 수행하는 if는 삼항 연산자로 대체할 수 없습니다. 다음은 컴파일되지 않습니다:

stock > 0 ? Ship(order) : Refund(order);
// error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement

else 없는 삼항 연산자도 없습니다. 식은 두 경우 모두 값을 가져야 하므로 ?와 :는 항상 필요합니다.

두 분기에는 공통 타입이 필요합니다

컴파일러는 두 분기로부터 식 전체의 타입을 결정합니다. 한 분기가 다른 분기의 타입으로 암시적으로 변환되어야 합니다. 어느 쪽도 안 되면 CS0173이 발생합니다:

var discount = isMember ? 10 : null;
// error CS0173: Type of conditional expression cannot be determined because
// there is no implicit conversion between 'int' and '<null>'

var label = inStock ? 1 : "none";
// error CS0173: ... between 'int' and 'string'

해결책은 원하는 타입으로 한 분기를 캐스트해서 다른 분기가 그 타입으로 변환되게 하는 것입니다:

출력:

no discount
7
3.5

points / 2.0 : 0에는 캐스트가 필요 없습니다. 0은 int이고 double로 암시적 변환되므로 식은 double이 됩니다.

C# 9는 대상 타입 지정을 추가했습니다. 식이 선언된 타입에 대입되면 컴파일러가 그 타입을 씁니다. int? discount = isMember ? 10 : null;은 C# 9 이상에서 컴파일됩니다. var에는 대상 타입이 없으므로 var discount = isMember ? 10 : null;은 모든 버전에서 여전히 실패합니다.

우선순위와 괄호

조건 연산자의 우선순위는 +, ==, &&보다 낮은, 아주 낮은 순위입니다. a > b ? a : b에서는 편리하지만 문자열 이어 붙이기 안에서는 놀랍게 동작합니다:

Console.WriteLine("Status: " + isActive ? "on" : "off");
// error CS0029: Cannot implicitly convert type 'string' to 'bool'

컴파일러는 조건을 문자열인 "Status: " + isActive로 읽습니다. 삼항 연산자를 괄호로 감싸면 해결됩니다: "Status: " + (isActive ? "on" : "off").

문자열 보간에는 고유한 함정이 있습니다. { } 안에서 콜론은 서식 지정자({price:F2})를 시작하므로, 괄호 없는 삼항 연산자는 파서를 혼란스럽게 합니다. 괄호로 감싸세요:

출력:

2 users online
You have 1 unread message
Status: online

중첩 삼항 연산자

이 연산자는 오른쪽 결합이므로 a ? x : b ? y : z는 a ? x : (b ? y : z)로 묶입니다. 조건을 한 줄에 하나씩 배치한 짧은 체인은 읽을 만합니다:

출력:

512 B
20 KB
5 MB

두세 단계를 넘거나 조건이 서로 다른 것을 검사한다면 if/else if나 switch 식으로 바꾸세요. 참 분기 안에 중첩하는 형태(a ? (b ? x : y) : z)가 가장 먼저 따라가기 어려워집니다.

??와 ?.가 흔한 삼항 연산자를 대체합니다

많은 삼항 연산자는 null을 처리하기 위해서만 존재합니다. C#에는 이를 위한 더 짧은 연산자가 있습니다:

삼항 연산자짧은 형태의미
name != null ? name : "guest"name ?? "guest"null일 때 대체값
customer != null ? customer.Email : nullcustomer?.Emailnull을 허용하는 멤버 접근
c != null && c.Address != null ? c.Address.City : "unknown"c?.Address?.City ?? "unknown"둘을 조합
value != null ? value : throw new ArgumentNullException(...)value ?? throw new ArgumentNullException(...)빠른 실패

출력:

Lena from Berlin
guest from unknown
guest from unknown

throw 식은 삼항 연산자의 어느 분기에도 쓸 수 있습니다: int age = input >= 0 ? input : throw new ArgumentOutOfRangeException(nameof(input));.

ref 조건식

C# 7.2부터 삼항 연산자는 두 값이 아니라 두 변수 중 하나를 고를 수 있고, 결과를 통해 대입할 수 있습니다:

// C# 7.2 and later
int wins = 0, losses = 0;
bool won = true;

ref int counter = ref (won ? ref wins : ref losses);
counter++;               // increments wins

두 분기 모두 같은 타입의 변수에 대한 ref여야 합니다. 애플리케이션 코드에서는 드물며, 주로 큰 구조체나 배열을 복사하지 않고 다루는 성능 중심 코드를 위해 존재합니다.

자주 묻는 질문

C#의 삼항 연산자란 무엇인가요?

조건 연산자 condition ? valueIfTrue : valueIfFalse로, 피연산자가 세 개인 유일한 C# 연산자입니다. 조건을 평가한 뒤 두 값 중 정확히 하나만 평가해 반환합니다: string label = age >= 18 ? "adult" : "minor";.

C#에서 else 없이 삼항 연산자를 쓸 수 있나요?

아니요. 식은 항상 값을 만들어야 하므로 ?와 : 부분이 모두 필요합니다. 조건이 참일 때만 무언가를 하고 싶다면 if 문을 쓰세요. 삼항 연산자는 단독 문장이 될 수도 없습니다. x > 0 ? A() : B();는 CS0201 오류입니다.

"Type of conditional expression cannot be determined" 오류가 나는 이유는 무엇인가요?

두 분기에 공통 타입이 없기 때문입니다. 예를 들어 var x = found ? 42 : null;(int와 null)이나 found ? 1 : "none"입니다. 타입이 존재하도록 한 분기를 캐스트하세요: found ? (int?)42 : null. C# 9부터는 선언된 대상 타입도 동작합니다. int? x = found ? 42 : null;은 컴파일되지만 var는 여전히 실패합니다.

문자열 보간 안에서 삼항 연산자는 어떻게 쓰나요?

괄호로 감싸세요: $"{(count == 1 ? "item" : "items")}". 괄호가 없으면 :이 서식 지정자의 시작으로 읽혀서 코드가 컴파일되지 않습니다.

C#에서 삼항 연산자를 중첩할 수 있나요?

네. 이 연산자는 오른쪽 결합이므로 a ? x : b ? y : z는 a ? x : (b ? y : z)라는 뜻입니다. 한 단계 중첩을 여러 줄에 나눠 쓰면 읽을 만하지만, 더 깊은 체인은 if/else if 체인이나 switch 식이 더 명확합니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기