C# 공식 문서: 개념, 예제, 실습
예제 중심의 간결한 C# 레퍼런스. 개념을 읽고 코드를 본 뒤 Coddy 여정에서 직접 연습하세요.
C# 가이드 학습 시작하기시작하기
- C#이란C#은 Microsoft가 만든 정적 타입 범용 언어로 .NET 위에서 실행됩니다. 어디에 쓰이는지, .NET과 어떤 관계인지, 버전마다 무엇이 추가되었는지, C# 프로그램이 어떻게 구성되는지 알아봅니다.
- Hello World첫 C# 프로그램을 한 줄씩 살펴봅니다: Main 메서드, Console.WriteLine과 Write, Console.ReadLine으로 입력 받기, 주석, dotnet CLI로 프로젝트 만들고 실행하기, 최상위 문.
- C# vs JavaC#과 Java는 모양이 비슷하고 비슷한 문제를 풉니다. 진짜 차이는 속성, 값 형식, 제네릭, LINQ와 스트림, async, 예외, 그리고 각 언어를 둘러싼 생태계에 있습니다. 코드로 나란히 비교합니다.
- C# vs C++C#은 가비지 컬렉터가 있는 관리형 언어이고, C++는 네이티브 코드로 컴파일되며 메모리를 직접 제어합니다. 그 차이가 안전성, 성능, 게임 엔진, 빌드 모델, 학습 난이도에서 어떻게 드러나는지 알아봅니다.
- 네임스페이스와 usingC# 네임스페이스가 타입을 어떻게 구성하고 using 지시문이 이를 어떻게 가져오는지 알아봅니다: 별칭, using static, 정규화된 이름, 모호한 참조 해결, global using, 파일 범위 네임스페이스.
기초
- 변수C#에서 변수를 선언하고 값을 대입하는 방법, var를 쓰면 좋을 때와 아닐 때, 범위가 동작하는 방식, const와 readonly와 static readonly의 차이, 명명 규칙, 할당되지 않은 변수 오류를 알아봅니다.
- 자료형C#의 모든 기본 제공 타입과 크기, 범위, 리터럴 접미사, 값 형식과 참조 형식의 차이, 금액에 decimal을 쓰는 이유, 정수 오버플로와 checked, 기본값을 정리합니다.
- 문자열 int 변환C#에서 int.Parse, int.TryParse, Convert.ToInt32로 문자열을 int로 바꾸는 방법, 암시적 캐스트와 명시적 캐스트의 동작, double을 캐스트하면 소수점이 잘리는 이유, as와 is 연산자, 그리고 "1.5"가 15로 파싱될 수 있는 이유를 알아봅니다.
- 연산자C# 연산자의 정확한 동작: 정수 나눗셈, 음수에서의 나머지 연산자, 증감 연산자와 복합 대입, 비교, 단락 평가 논리 연산, 비트 연산자, null 연산자, 우선순위 표를 정리합니다.
- 문자열C#에서 문자열이 동작하는 방식: 불변성, Length와 인덱싱, ==와 Equals와 string.Compare로 비교하기, 이스케이프 문자, 축자 @ 문자열과 여러 줄 텍스트, 원시 문자열 리터럴, null과 빈 문자열 검사, 문자 순회를 알아봅니다.
- SubstringC#에서 String.Substring이 동작하는 방식: 시작 인덱스와 길이 인수, ArgumentOutOfRangeException과 이를 피하는 방법, IndexOf 결과 주변의 텍스트 추출, 앞이나 뒤의 n글자 가져오기, C# 8의 범위 연산자를 알아봅니다.
- 문자열 보간C#의 $"..." 문자열 보간이 동작하는 방식: 중괄호 안의 식, F2, N0, C, D5, X 같은 서식 지정자, 표를 위한 정렬, 중괄호 이스케이프, string.Format과 복합 서식, 축자와 원시 보간 문자열, 특정 문화권에 맞춘 서식을 알아봅니다.
- 문자열 메서드C#에서 매일 쓰는 String 메서드: 여러 구분자와 옵션을 쓰는 Split, Replace, Contains와 대소문자 무시 검색, IndexOf와 LastIndexOf, StartsWith와 EndsWith, Trim, ToUpper와 ToLower, PadLeft, Join, Concat, 문자열 뒤집기를 알아봅니다.
- StringBuilderC#에서 루프 안의 반복적인 문자열 이어 붙이기가 느린 이유와 StringBuilder가 이를 해결하는 방법: Append, AppendLine, AppendFormat, Insert, Remove, Replace, 용량, 그리고 일반 이어 붙이기나 string.Join이 더 나은 경우를 알아봅니다.
- 랜덤 난수C#에서 Random 클래스로 난수를 만드는 방법: Next와 포함되지 않는 상한, 범위 안의 랜덤 double, 재현 가능한 결과를 위한 시드, 랜덤 요소 고르기, 리스트 셔플, 루프 안의 new Random 함정, 암호학적으로 안전한 난수를 알아봅니다.
- DateTimeC#에서 날짜와 시간 다루기: DateTime 값 만들기, Now와 UtcNow와 Today의 차이, 날짜와 월 더하기, 빼서 TimeSpan 얻기, TotalHours와 Hours의 차이, 날짜 비교, DayOfWeek, ParseExact와 TryParse로 파싱하기, DateTimeOffset, DateOnly를 알아봅니다.
- DateTime 서식C#에서 DateTime을 문자열로 서식화하는 방법: 표준 서식 문자열(d, D, o, s, u, t), yyyy-MM-dd HH:mm:ss 같은 사용자 지정 패턴, 전체 지정자 표, 문화권이 출력을 바꾸는 방식, "o"를 이용한 ISO 8601 왕복, mm과 MM, hh와 HH 실수를 알아봅니다.
제어 흐름
- if elseC#에서 if, else if, else가 동작하는 방식: 조건이 bool이어야 하는 이유, &&, ||, !로 검사를 조합하기, 중괄호가 중요한 경우, 깊은 중첩 대신 보호 절, 그리고 =와 ==, 잘못 붙은 세미콜론 버그를 알아봅니다.
- switch 문C# switch 문이 동작하는 방식: int, string, char, enum 값으로 분기하기, 모든 case에 break가 필요한 이유(CS0163 오류), 여러 값을 위한 case 레이블 겹치기, goto case, default, 문자열의 대소문자 구분, when을 쓰는 패턴 case 레이블을 알아봅니다.
- switch 식C# 8의 switch 식은 case와 break 대신 패턴 갈래로 값을 결과로 바꿉니다. 문법, _ 무시 항목, 속성/튜플/관계 패턴, and/or/not, when 가드, 완전성 경고와 SwitchExpressionException을 실행 가능한 C# 7 대응 코드와 함께 알아봅니다.
- 삼항 연산자C# 조건 연산자 condition ? a : b는 두 값 중 하나를 고릅니다. 평가 방식, 두 분기에 공통 타입이 필요한 이유(CS0173)와 이를 고치는 캐스트, 중첩 삼항 연산자, 문자열 보간 안의 괄호, 그리고 ??와 ?.가 더 잘 표현하는 경우를 알아봅니다.
- for 반복문C# for 루프가 동작하는 방식: 초기화 식, 조건, 반복자, 거꾸로 세기와 간격 지정, 여러 루프 변수, 인덱스로 배열 순회, 중첩 루프, for(;;), 그리고 하나 차이 오류와 순회 중 삭제 버그를 알아봅니다.
- foreach 반복문C#에서 foreach가 동작하는 방식: 배열, 리스트, 문자열, 딕셔너리(KeyValuePair와 분해) 순회, 인덱스 얻기, 루프 변수가 읽기 전용인 이유, "Collection was modified" InvalidOperationException과 해결법, foreach에 쓰려면 타입에 무엇이 필요한지 알아봅니다.
- while 반복문C#에서 while과 do while 루프가 동작하는 방식: 본문 전이나 후에 검사하는 조건, 종료 값이나 입력 끝까지 입력 읽기, break를 쓰는 while (true), 그리고 사람들이 자주 빠지는 무한 루프 실수를 알아봅니다.
- break와 continueC#에서 break, continue, goto가 동작하는 방식: 루프를 일찍 끝내기, 반복 하나 건너뛰기, switch 안의 break, 중첩 루프를 빠져나가는 세 가지 방법(플래그, goto, return을 쓰는 메서드), 그리고 while 안의 continue가 만드는 무한 루프 버그를 알아봅니다.
메서드와 델리게이트
- 메서드C#에서 메서드를 선언하고 호출하는 방법: 반환 타입과 void, 매개변수, 식 본문 메서드, 튜플로 여러 값 반환하기, 오버로딩, params 배열, 정적 메서드와 인스턴스 메서드, Main 메서드, 재귀, 로컬 함수를 알아봅니다.
- 선택적 매개변수C#에서 선택적 매개변수와 명명된 인수가 동작하는 방식: 기본값과 컴파일 시점 상수 규칙, 매개변수 순서, 이름으로 인수 건너뛰기, 선택적 매개변수와 오버로드 비교, 호출자 정보 특성, 그리고 호출하는 쪽에 기본값이 새겨지는 버전 관리 함정을 알아봅니다.
- ref와 outC#에서 ref, out, in 매개변수가 동작하는 방식: 복사본 대신 변수 자체를 넘기기, 추가 결과를 위한 out과 TryParse 패턴, out var, 읽기 전용 in 매개변수, 확정 할당 규칙, 그리고 인수가 참조 형식일 때 달라지는 점을 알아봅니다.
- 람다 식C#에서 람다 식이 동작하는 방식: 모든 형태의 => 문법, Func와 Action에 람다 저장하기, 메서드와 LINQ에 넘기기, 문 람다, 캡처된 변수에 대한 클로저, 그리고 foreach에는 없는 for 루프 캡처 버그를 알아봅니다.
- 델리게이트C#의 델리게이트가 무엇이고 어떻게 쓰는지: 델리게이트 타입 선언, 메서드나 람다나 익명 메서드로 만들기, 호출하기, 콜백으로서의 델리게이트, +=와 -=를 쓰는 멀티캐스트 델리게이트, ?.Invoke로 null 검사하기, 그리고 델리게이트와 Func, Action, 이벤트의 관계를 알아봅니다.
- Func와 ActionC#에서 Func와 Action이 동작하는 방식: 값을 반환하는 함수를 위한 Func<T, TResult>, 반환하지 않는 함수를 위한 Action<T>, Predicate와 Comparison, 함수를 넘기고 반환하기, 명령 딕셔너리, async 람다, 그리고 직접 델리게이트를 선언해야 할 때를 알아봅니다.
- 이벤트C#에서 이벤트가 동작하는 방식: EventHandler와 EventHandler<T>로 이벤트 선언하기, 사용자 정의 EventArgs 클래스, +=와 -=로 구독과 구독 해제, ?.Invoke로 안전하게 이벤트 발생시키기, public 델리게이트 필드보다 이벤트가 나은 이유, 구독 해제를 잊어서 생기는 메모리 누수를 알아봅니다.
- 확장 메서드C#에서 확장 메서드가 동작하는 방식: 첫 매개변수에 this를 붙인 정적 클래스의 정적 메서드, 인스턴스 메서드처럼 호출하기, string과 열거형과 IEnumerable<T> 확장하기, LINQ가 확장 메서드로 만들어진 방식, 인스턴스 메서드가 우선하는 이유, null 수신자, 필요한 using 지시문을 알아봅니다.
컬렉션과 LINQ
- 배열C# 배열은 한 타입의 요소를 담는 고정 크기 블록입니다. 배열을 선언하고 초기화하는 방법, Length 읽기, Array 클래스로 정렬하고 검색하기, 안전하게 복사하기, 2차원 배열과 가변 배열 다루기를 알아봅니다.
- ListList<T>는 C#의 늘어나는 배열입니다. 리스트를 만들고, 항목을 추가하고 삽입하고, 값이나 인덱스나 조건으로 제거하고, Contains와 Find로 검색하고, 속성으로 정렬하고, foreach 안에서 리스트를 바꿀 때 나는 오류를 피하는 방법을 알아봅니다.
- DictionaryDictionary<TKey, TValue>는 키를 값에 대응시키고 빠르게 조회합니다. 항목을 추가하고 갱신하는 방법, TryGetValue로 안전하게 읽기, KeyValuePair 항목 순회하기, 개수 세기, 키의 대소문자 무시하기, SortedDictionary로 키를 정렬된 상태로 유지하기를 알아봅니다.
- HashSetHashSet<T>는 고유한 항목을 담고 Contains에 상수 시간으로 답합니다. Add가 중복을 알려 주는 방식, 리스트에서 중복을 제거하는 방법, 집합의 합집합, 교집합, 차집합을 구하는 방법, 직접 만든 객체를 값으로 비교하게 만드는 방법을 알아봅니다.
- QueueQueue<T>는 선입선출 컬렉션으로, 항목이 도착한 순서대로 나갑니다. Enqueue, Dequeue, Peek, 빈 큐 예외와 TryDequeue, 큐를 이용한 너비 우선 탐색, 그리고 ConcurrentQueue나 PriorityQueue를 써야 할 때를 알아봅니다.
- StackStack<T>는 후입선출 컬렉션으로, 가장 최근에 추가한 항목이 먼저 나옵니다. Push, Pop, Peek, 빈 스택 예외와 TryPop, 스택이 거꾸로 열거되는 이유, 그리고 두 가지 전형적인 용도인 실행 취소 기록과 괄호 짝 검사를 알아봅니다.
- 튜플C# 튜플은 타입을 선언하지 않고 몇 개의 값을 하나로 묶습니다. 값 튜플 문법, 이름 붙은 요소, 메서드에서 여러 값 반환하기, var (a, b)로 분해하기, 딕셔너리의 튜플 키, 그리고 ValueTuple과 예전 System.Tuple의 차이를 알아봅니다.
- LINQLINQ는 C#의 모든 컬렉션에 쿼리 연산자를 더해 줍니다. 메서드 구문과 쿼리 구문, Where로 필터링, Select로 투영, OrderBy와 ThenBy로 정렬, GroupBy로 그룹화, First와 FirstOrDefault의 함정, 집계, 그리고 지연 실행이 쿼리 실행 시점을 어떻게 바꾸는지 알아봅니다.
- IEnumerable과 yieldIEnumerable<T>는 foreach와 LINQ 뒤에 있는 인터페이스이고, yield return은 이를 구현하는 가장 쉬운 방법입니다. IEnumerable과 ICollection, List의 관계, 반복기 메서드가 한 단계씩 실행되는 방식, yield break, 무한 시퀀스, 지연 평가의 함정을 알아봅니다.
클래스와 타입
- 클래스와 객체C#에서 클래스를 선언하고, 필드와 메서드를 주고, new로 객체를 만드는 방법, 그리고 클래스가 참조 형식이라는 것의 의미를 알아봅니다: 공유되는 객체, null과 NullReferenceException, ToString, 개체 이니셜라이저.
- 생성자C# 생성자가 동작하는 방식: 암시적 기본 생성자와 그것이 사라지는 때, 매개변수가 있는 생성자와 오버로드된 생성자, this(...)와 base(...)로 연결하기, 실행 순서, 정적 생성자와 private 생성자, C# 12 기본 생성자를 알아봅니다.
- 속성C# 속성이 동작하는 방식: 지원 필드 위의 get과 set 접근자, 자동 구현 속성, private set과 get 전용 속성, 계산 속성, setter에서의 검증, 그리고 C# 9와 11의 init과 required 키워드를 알아봅니다.
- static 키워드C#에서 static의 의미: 객체가 아니라 타입에 속하는 멤버, 인스턴스 간에 공유되는 상태, 정적 메서드와 정적 클래스, 정적 생성자, const와 static readonly의 차이, using static, 그리고 CS0120 오류를 알아봅니다.
- 접근 한정자C#의 여섯 가지 접근 한정자(public, private, protected, internal, protected internal, private protected), 각각이 허용하는 것, 아무것도 쓰지 않았을 때의 기본값, 그리고 이들이 만드는 컴파일러 오류를 알아봅니다.
- 상속C#에서 클래스 상속이 동작하는 방식: 콜론으로 파생하기, 상속되는 것과 상속되지 않는 것, base로 기본 클래스 호출하기, virtual과 override, new로 숨기기(모두를 놀라게 하는 출력과 함께), sealed, 다형성, 계층 구조에서의 업캐스트와 다운캐스트를 알아봅니다.
- 추상 클래스C#에서 추상 클래스란 무엇인가: 인스턴스화할 수 없고, 파생 클래스가 반드시 구현해야 하는 추상 멤버를 선언할 수 있는 기본 클래스입니다. 추상 메서드와 속성, 생성자, 템플릿 메서드 패턴, 그리고 대신 인터페이스를 골라야 할 때를 다룹니다.
- 인터페이스C#에서 인터페이스가 동작하는 방식: 선언하기, 클래스와 구조체에서 구현하기, 인터페이스를 타입으로 쓰기, 여러 개를 한 번에 구현하기, 명시적 구현, 가장 많이 구현하게 될 프레임워크 인터페이스(IComparable<T>, IEnumerable<T>, IDisposable), 그리고 C# 8 기본 인터페이스 메서드를 알아봅니다.
- 구조체C#에서 구조체가 동작하는 방식: 값 의미론과 대입 시 복사, List<T> 수정 오류, 생성자 규칙, 같음 비교, 박싱, readonly struct와 record struct, 그리고 구조체가 맞는 선택인 때에 대한 안내와 함께 struct와 class를 비교합니다.
- 열거형C#에서 열거형이 동작하는 방식: 이름 붙은 상수 선언, 기반 정수 값과 캐스트, Parse와 TryParse로 enum과 문자열을 서로 변환하기, 모든 값 나열하기, 비트 연산자와 HasFlag를 쓰는 [Flags], enum으로 switch하기, 정의되지 않은 값 처리를 알아봅니다.
- 레코드C# 레코드(C# 9 이상)란 무엇인가: 위치 구문, 컴파일러가 생성하는 멤버, 값 기반 같음 비교, with로 만드는 비파괴적 복사, 기본 제공 ToString, C# 10의 record struct, 레코드 간 상속, 그리고 직접 작성한 동등한 클래스를 알아봅니다.
- 제네릭C#에서 제네릭이 동작하는 방식: 형식 매개변수로 제네릭 클래스와 메서드 작성하기, 형식 유추, where 제약 조건(class, struct, new(), 기본 클래스, 인터페이스), default(T), 형식별 정적 멤버, IEnumerable<out T>의 공변성을 알아봅니다.
- nullable 형식C#의 null에 관한 모든 것: HasValue, Value, GetValueOrDefault를 가진 nullable 값 형식(int?, Nullable<T>), null 병합 연산자 ??와 ??=, null 조건부 연산자 ?., null 검사, 그리고 C# 8의 nullable 참조 형식(string?)을 알아봅니다.
- 패턴 매칭C#에서 패턴 매칭이 동작하는 방식: 타입 패턴과 상수 패턴을 쓰는 is 연산자, case 패턴과 when을 쓰는 switch 문, switch 식, 속성/튜플/위치 패턴, 관계 패턴과 논리 패턴(and, or, not), 목록 패턴, 그리고 각각을 추가한 C# 버전을 알아봅니다.
예외
- try catchtry, catch, finally는 C#이 예외를 처리하는 방법입니다. 예외가 호출 스택을 따라 올라가는 방식, 특정 예외 타입을 올바른 순서로 잡는 방법, when으로 필터링하기, finally에서 정리하기, 스택 추적을 잃지 않고 throw;로 다시 던지기, 그리고 잡지 말아야 할 예외를 알아봅니다.
- 예외 던지기C#에서 예외를 언제 어떻게 던지는지 알아봅니다. throw 문, 어떤 실수에 어떤 기본 제공 예외 타입이 맞는지, ??와 ?:를 쓰는 throw 식, ThrowIfNull 헬퍼, 그리고 자기 데이터와 내부 예외를 가진 사용자 정의 예외 클래스를 작성하는 방법입니다.
- using 문using 문은 예외가 발생하더라도 파일, 스트림, 연결 등 IDisposable 객체에 Dispose가 호출되도록 보장합니다. 무엇으로 컴파일되는지, 객체가 dispose되는 순서, C# 8의 using 선언, 직접 만든 클래스에 IDisposable을 구현하는 방법을 알아봅니다.
비동기와 스레드
- async awaitasync와 await를 쓰면 C# 코드가 스레드를 막지 않고 느린 작업을 기다릴 수 있습니다. 비동기 메서드가 실행되는 방식, Task와 Task<T>, Task.WhenAll로 독립적인 작업을 동시에 실행하기, 비동기 코드의 예외, 그리고 async void와 .Result가 버그와 교착 상태를 일으키는 이유를 알아봅니다.
- 태스크Task는 .NET에서 비동기 작업과 병렬 작업의 단위입니다. CPU 중심 작업을 위한 Task.Run, 결과 읽기, WhenAll과 WhenAny로 태스크 결합하기, 연속 작업, CancellationToken으로 취소하기, Task와 Thread의 차이, 그리고 Parallel.For가 더 잘 맞는 경우를 알아봅니다.
- lock 문lock 문은 한 번에 한 스레드만 코드 블록을 실행하게 합니다. 경쟁 상태가 카운터를 망가뜨리는 모습을 보고, lock으로 고치고, 어떤 객체를 잠글지, 단순한 카운터에 Interlocked 쓰기, 잠금 순서로 인한 교착 상태 피하기, 안에서 await할 때 SemaphoreSlim 쓰기를 알아봅니다.
- 타이머C#에는 타이머 클래스가 여러 개 있고 동작이 서로 다릅니다. System.Threading.Timer, System.Timers.Timer, PeriodicTimer, UI 타이머를 언제 쓸지, 시작하고 멈추는 방법, 콜백이 스레드 풀에서 실행되고 겹칠 수 있는 이유, 타이머를 계속 참조해야 하는 이유, Stopwatch로 경과 시간을 재는 방법을 알아봅니다.
파일, JSON 등
- 파일 읽기와 쓰기C#에서 파일에 쓰고 파일을 읽는 방법: 파일 전체를 위한 File.WriteAllText, ReadAllText, AppendAllText, WriteAllLines, ReadAllLines, 큰 파일을 위한 StreamWriter와 StreamReader, Path.Combine을 쓰는 경로, 디렉터리, 인코딩, 그리고 파일 코드가 처리해야 하는 예외를 알아봅니다.
- JSONSystem.Text.Json으로 C#에서 JSON을 다루는 방법: JsonSerializer.Serialize와 Deserialize, camelCase와 들여쓰기 출력, JsonPropertyName과 JsonIgnore 같은 특성, 문자열로 쓰는 열거형, JsonDocument와 JsonNode로 클래스 없이 JSON 읽기, 오류, 그리고 Newtonsoft.Json과의 비교를 알아봅니다.
- 정규식System.Text.RegularExpressions로 C#에서 정규식을 쓰는 방법: IsMatch, Match와 Matches, 번호 그룹과 이름 있는 그룹, 치환 문자열과 람다를 쓰는 Replace, Split, RegexOptions, 축자 패턴 문자열, 입력 검증, 시간 제한, 그리고 .NET 7 GeneratedRegex 특성을 알아봅니다.
- 리플렉션과 특성C#에서 리플렉션이 동작하는 방식: typeof와 GetType으로 얻는 Type 객체, 속성 읽기와 설정, 이름으로 메서드 호출, 인스턴스 만들기, 그리고 특성: Obsolete 같은 기본 제공 특성, 직접 선언하기, 실행 시점에 읽기. 리플렉션의 비용도 알아봅니다.