연산자는 피연산자 하나, 둘 또는 셋을 받아 값을 만듭니다: a + b, !done, x ?? fallback. C# 연산자는 C, Java, JavaScript의 연산자와 비슷하지만, 몇몇은 처음 배우는 사람을 놀라게 하는 방식으로 동작합니다. 무엇보다 정수 나눗셈과 %의 부호가 그렇습니다.
산술 연산자
+, -, *, /, %는 모든 숫자 타입에 동작합니다. 결과 타입은 피연산자를 따릅니다. int 두 개는 int를, int와 double은 double을 만듭니다.
출력:
22
12
85
3
2
3.4
3.4
3
3.4
정수 나눗셈은 0 방향으로 잘라냅니다. 17 / 5는 3이고 -17 / 5는 -3입니다. 이것이 아주 흔한 버그를 만듭니다. int percent = done / total * 100;은 done < total이면 항상 0입니다. done / total이 이미 0이기 때문입니다. 곱셈을 먼저 하거나(done * 100 / total) double로 나누세요.
정수나 decimal을 0으로 나누면 DivideByZeroException이 발생합니다. double을 0으로 나누면 예외가 아니라 무한대가 되고, 0.0 / 0.0이면 NaN이 됩니다:
출력:
True
True
False
DivideByZeroException
나머지 연산자와 음수
%는 나눗셈의 나머지를 반환하며, 결과는 왼쪽 피연산자의 부호를 따릅니다. 이는 양수로 나누면 항상 0 이상인 Python이나 수학의 "mod"와 다릅니다:
출력:
1
-1
1
2
even
62 min 5 s
Thu
1.5
왼쪽이 음수가 될 수 있고 결과를 인덱스로 쓸 때는 언제나 Mod 헬퍼를 쓰세요. days[(today + offset) % 7]은 합이 음수이면 IndexOutOfRangeException을 던집니다. 홀수 검사에도 같은 함정이 있습니다. n % 2 == 1은 음의 홀수에서 false이므로 n % 2 != 0으로 검사하세요.
대입, 복합 대입, 증감
=는 대입합니다. 복합 형태 +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=는 연산과 대입을 합칩니다. ++와 --는 1을 더하거나 빼며, 반환하는 값이 다른 전위형과 후위형이 있습니다:
출력:
14
start, step
a=5 b=7 i=7
4
byte 사례는 특이한 점을 보여 줍니다. byte + int는 int이므로 level = level + 10;은 컴파일되지 않지만, 복합 대입에는 변수 타입으로의 암시적 캐스트가 포함되어 있으므로 level += 10;은 컴파일됩니다. 이 캐스트는 오버플로 시 값이 돌아갑니다(250 + 10이 4가 됩니다).
비교 연산자와 논리 연산자
비교 연산자(==, !=, <, >, <=, >=)는 bool을 반환합니다. 문자열에서 ==는 참조가 아니라 텍스트를 비교합니다. 논리 연산자는 !(not), &&(and), ||(or), ^(배타적 or)입니다.
출력:
True
no active user
with ||:
evaluated A
with |:
evaluated A
evaluated B
True
&&와 ||는 단락 평가를 합니다. 오른쪽 피연산자는 결과를 바꿀 수 있을 때만 실행됩니다. 그래서 user != null && user.IsActive가 안전합니다. 불리언에 쓰는 한 글자 &와 |는 항상 양쪽을 평가하며, 조건에서 원하는 동작인 경우는 드뭅니다.
비트 연산자와 시프트 연산자
정수 타입에서 &, |, ^, ~는 개별 비트에 동작하고, <<와 >>는 비트를 이동시킵니다. 플래그, 마스크, 해싱, 저수준 프로토콜에 쓰입니다:
출력:
3
True
False
101
1024
125
5
-1
이름 붙은 플래그 집합에는 정수 상수보다 [Flags] 특성을 붙인 열거형이 더 명확하고, 마스크보다 HasFlag가 더 읽기 좋습니다. C# 11은 음수에서도 0으로 채우는 부호 없는 오른쪽 시프트 >>>를 추가했습니다.
null 연산자
null일 수 있는 값을 다루기 위한 연산자가 세 가지 있습니다:
출력:
Guest
0
5
a ?? b는 a가 null이 아니면 a를, null이면 b를 반환합니다. a?.Member는 a가 null이면 예외 대신 null을 반환합니다. C# 8은 a가 null일 때만 a에 b를 대입하는 a ??= b를 추가했습니다. 자세한 내용은 nullable 형식에서 다룹니다.
연산자 우선순위
식에 여러 연산자가 섞이면 위쪽 행이 더 강하게 결합합니다. 같은 행의 연산자는 왼쪽에서 오른쪽으로 묶이지만, 대입, ??, ?:는 오른쪽에서 왼쪽으로 묶입니다. (피연산자 자체는 항상 왼쪽부터 평가됩니다.)
| 분류 | 연산자 |
|---|---|
| 기본 | x.y, x?.y, f(x), a[i], x++, x--, new, typeof, nameof |
| 단항 | +x, -x, !x, ~x, ++x, --x, (T)x, await |
| 곱셈 | *, /, % |
| 덧셈 | +, - |
| 시프트 | <<, >>, >>> |
| 관계와 타입 검사 | <, >, <=, >=, is, as |
| 같음 | ==, != |
| 비트 AND | & |
| 비트 XOR | ^ |
| 비트 OR | | |
| 조건부 AND | && |
| 조건부 OR | || |
| null 병합 | ?? |
| 조건 | c ? a : b |
| 대입과 람다 | =, +=, -=, ??=, =>, 그 밖의 복합 형태 |
알아 둘 결과가 두 가지 있습니다. a + b * c는 곱셈을 먼저 하고, x & mask == 0은 비교를 먼저 합니다(==가 &보다 강하게 결합). 그래서 x & (mask == 0)이라는 뜻이 되고 정수에서는 컴파일되지 않습니다. (x & mask) == 0이라고 쓰세요. 괄호는 비용이 없고 의도를 분명하게 합니다.
연산자 오버로딩
직접 만든 타입에서 연산자의 의미를 정의할 수 있습니다. decimal, DateTime, TimeSpan이 +와 <를 지원하는 방법이 이것입니다:
출력:
33.49
True
연산자는 public static이어야 하고, 일부는 반드시 짝으로 정의해야 합니다. <를 정의하면 >가, ==를 정의하면 !=가 필요합니다. ==를 정의하면 이에 맞게 Equals와 GetHashCode를 재정의할 때까지 컴파일러가 경고합니다. 숫자, 벡터, 금액처럼 의미가 분명한 곳에서만 연산자를 오버로딩하세요.
자주 묻는 질문
C#에서 %는 무엇을 하나요?
%는 나머지 연산자입니다. 17 % 5는 2입니다. 결과는 왼쪽 피연산자의 부호를 따르므로 -7 % 3은 Python처럼 2가 아니라 -1입니다. 인덱스를 순환시킬 때처럼 항상 0 이상인 결과가 필요하면 ((a % n) + n) % n이라고 쓰세요. double과 decimal에도 동작합니다: 7.5 % 2는 1.5입니다.
C#에서 7 / 2가 3인 이유는 무엇인가요?
두 피연산자가 모두 정수이면 /는 정수 나눗셈을 하고 소수 부분을 0 방향으로 잘라 버립니다. 소수 결과를 얻으려면 피연산자 하나를 부동소수점이나 decimal 값으로 만드세요. 7 / 2.0은 3.5, (double)a / b는 나누기 전에 변환하고, 7m / 2는 decimal 3.5입니다.
C#에서 &&와 &의 차이는 무엇인가요?
&&는 조건부 AND로, 왼쪽이 false이면 오른쪽을 평가하지 않습니다. 불리언에 쓴 &는 항상 양쪽을 평가하고, 정수에 쓰면 비트 AND입니다. 조건에는 &&와 ||를 쓰세요. 특히 user != null && user.IsActive 같은 보호 조건은 &로 쓰면 NullReferenceException을 던집니다.
C#에서 i++와 ++i의 차이는 무엇인가요?
둘 다 i에 1을 더합니다. 식 i++는 이전 값으로, ++i는 새 값으로 평가됩니다. 한 줄에 단독으로 쓰면(i++;) 차이가 없습니다. int a = i++;처럼 결과를 사용할 때만 차이가 나므로, 많은 스타일 가이드가 증감을 별도 줄에 두라고 합니다.
C#의 연산자 우선순위는 어떻게 되나요?
높은 것부터 낮은 순서로: 기본(x.y, f(x), a[i], x++), 단항(!, -, ++x, 캐스트), 곱셈(* / %), 덧셈(+ -), 시프트, 관계(< > <= >= is as), 같음(== !=), &, ^, |, &&, ||, ??, 조건 ?:, 마지막으로 대입입니다. 확실하지 않으면 괄호를 추가하세요.