C# · 심층 가이드
C# Hello World 완전 정리
최상위 문으로 줄인 C# 진입점에서 문자열 보간, 명령행 인자, 환경 변수, 콘솔 인코딩까지 열 가지 최소 예제로 첫 프로그램의 동작을 확인합니다.
C#의 첫 프로그램은 다른 언어보다 사연이 깁니다. 오랫동안 class Program과 static void Main을 갖춘 골격이 있어야 실행이 됐지만, 최상위 문(top-level statements)이 들어오면서 Console.WriteLine 한 줄짜리 파일도 그대로 컴파일됩니다. 겉모습은 스크립트 언어처럼 단순해졌어도 컴파일러가 뒤에서 진입점 클래스를 만들어 준다는 사실은 그대로여서, 이 골격을 한 번은 들여다볼 값어치가 있습니다.
예제는 출력이라는 한 가지 동작을 조금씩 비틀며 이어집니다. Hello World 출력에서 시작해 변수 값을 같이 출력과 템플릿 문자열 인사로 넘어가면 문자열 보간이 왜 + 연결보다 읽기 편한지 눈에 들어옵니다. 뒤쪽의 명령행 인자 읽기와 환경 변수 읽기는 프로그램 바깥에서 값을 받아오는 두 통로를 나란히 보여 주므로, 따로 떼지 말고 붙여서 읽는 편이 낫습니다. 함수로 인사 만들기와 배열을 순회하며 인사까지 오면 출력 한 줄이 재사용 가능한 단위로 정리되는 과정이 한눈에 들어옵니다.
유니코드 메시지 출력에서 한글이나 이모지가 물음표로 깨진다면 코드가 아니라 콘솔 인코딩을 의심해야 합니다. 터미널의 기본 코드 페이지가 UTF-8이 아닐 때 생기는 현상이고, Console.OutputEncoding을 UTF-8로 지정하면 정리됩니다. 하나 더, .NET의 args에는 실행 파일 경로가 들어 있지 않습니다. C나 C++ 습관대로 args[0]을 프로그램 이름으로 여기면 사용자가 넘긴 첫 인자를 통째로 놓칩니다.
01Hello World 출력
Console.WriteLine 한 줄로 문자열을 표준 출력에 내보내고 줄바꿈까지 붙입니다. 실행 환경이 제대로 갖춰졌는지 확인하는 첫 코드로 씁니다. 클래스와 Main이 보이지 않는 이유는 최상위 문 문법 덕분이며, 이 방식은 프로젝트 안에서 파일 하나에만 허용됩니다.
C# code
// Hello World 출력
using System;
Console.WriteLine("Hello, World 1!");출력할 줄이 아주 많다면 Console.Out의 자동 플러시를 끄고 마지막에 한 번만 비우는 편이, 줄마다 스트림을 비우는 기본 동작보다 훨씬 빠릅니다.
출력이 파이프로 연결된 상태에서 상대 프로그램이 먼저 종료하면 쓰기에서 IOException이 납니다. 다른 명령에 결과를 넘기는 도구라면 이 경우를 다뤄야 합니다.
02변수 값을 같이 출력
APP_ENV 환경 변수를 읽어 값이 있으면 그대로, 없으면 대체 문자열을 출력합니다. 설정값에 기본값을 얹는 전형적인 방식입니다. ??는 null일 때만 오른쪽으로 넘어가므로, 변수가 빈 문자열로 설정된 경우까지 걸러 내려면 string.IsNullOrEmpty가 필요합니다.
C# code
// 변수 값을 같이 출력
using System;
Console.WriteLine(Environment.GetEnvironmentVariable("APP_ENV") ?? $"run-2");Environment.GetEnvironmentVariable은 정의되지 않은 이름에 대해 빈 문자열이 아니라 null을 돌려주므로 ??가 의도대로 동작합니다.
프로세스가 시작된 뒤 바깥에서 바꾼 환경 변수는 이미 실행 중인 프로그램에 반영되지 않습니다. 값을 다시 읽으려면 프로세스를 재시작해야 합니다.
03함수로 인사 만들기
인사말을 만드는 함수는 아직 없고 완성된 문자열을 바로 출력합니다. 이 줄을 지역 함수로 감싸면 이름과 인사 형식을 분리할 수 있습니다. 최상위 문에서는 지역 함수를 명령문 사이 어디에 두든 상관없고, 선언보다 앞줄에서 호출해도 정상적으로 컴파일됩니다.
C# code
// 함수로 인사 만들기
using System;
Console.WriteLine("Hello, World 3!");인사 문구처럼 반복되는 형식은 상수 형식 문자열로 분리해 두면 문구를 바꾸거나 번역을 붙일 때 한 곳만 고치면 됩니다.
문자열을 +로 이어 붙여 인사말을 만들면 이름이 null이어도 예외 없이 빈 자리로 이어져, 이름 없는 인사말이 그대로 출력됩니다.
04배열을 순회하며 인사
배열도 반복문도 없이 문자열 하나만 출력하는 코드입니다. 여러 이름을 처리하려면 foreach로 감싸면 됩니다. 다만 반복마다 Console.WriteLine을 부르면 매번 출력 스트림을 거치므로, 줄 수가 많을 때는 string.Join으로 한 번에 묶어 내보내는 쪽이 빠릅니다.
C# code
// 배열을 순회하며 인사
using System;
Console.WriteLine(Environment.GetEnvironmentVariable("APP_ENV") ?? $"run-4");foreach는 배열에 대해 컴파일러가 인덱스 순회로 바꿔 주어 열거자 객체가 만들어지지 않지만, 같은 배열을 IEnumerable로 받으면 열거자가 할당됩니다.
string.Join으로 묶어 한 번에 출력하면 빠르지만 전체 문자열이 한꺼번에 메모리에 올라옵니다. 줄 수가 아주 많다면 일정 개수마다 나눠 내보내야 합니다.
05명령행 인자 읽기
명령행 인자를 읽는 부분은 없고 고정 문자열을 출력합니다. 최상위 문으로 작성한 프로그램에서는 args라는 문자열 배열을 선언 없이 바로 쓸 수 있습니다. C나 일부 언어와 달리 이 배열에는 실행 파일 이름이 들어 있지 않고, 첫 원소부터 사용자가 넘긴 인자입니다.
C# code
// 명령행 인자 읽기
using System;
Console.WriteLine("Hello, World 5!");옵션이 몇 개를 넘어가면 직접 파싱하기보다 인자 파서를 쓰는 편이 낫고, 최소한 사용법 안내와 잘못된 인자에 대한 오류 메시지는 갖춰 두어야 합니다.
인자 비교를 ==로 하면 대소문자와 앞뒤 공백이 그대로 반영되어 사용자가 대문자를 섞어 적으면 인식되지 않습니다. 비교자를 지정하는 편이 안전합니다.
06템플릿 문자열 인사
출력식에 $가 붙어 있지만 중괄호로 끼워 넣은 값이 없어 사실상 일반 문자열과 같습니다. 값을 넣으려면 중괄호 안에 식을 적으면 됩니다. 중괄호 문자 자체를 찍고 싶을 때는 두 번 겹쳐 적어야 하며, 그러지 않으면 형식 오류로 실행 중에 예외가 납니다.
C# code
// 템플릿 문자열 인사
using System;
Console.WriteLine(Environment.GetEnvironmentVariable("APP_ENV") ?? $"run-6");보간 문자열 안에서는 형식 지정자와 정렬 폭을 함께 줄 수 있어, 숫자를 자릿수에 맞춰 찍거나 표 형태로 정렬된 출력을 만들 때 유용합니다.
보간 문자열은 값마다 ToString을 부르므로, 실제로 출력되지 않을 수도 있는 로그 자리에 쓰면 필요 없는 문자열 조립 비용이 매번 발생합니다.
07유니코드 메시지 출력
출력 자체는 ASCII 문자열이지만, 한글이나 이모지를 넣어도 문자열 리터럴은 UTF-16으로 저장됩니다. 다국어 메시지를 찍을 때 쓰는 자리입니다. 윈도우 콘솔에서 깨져 보인다면 문자열이 아니라 콘솔 코드 페이지가 원인이므로 Console.OutputEncoding을 UTF-8로 맞추면 됩니다.
C# code
// 유니코드 메시지 출력
using System;
Console.WriteLine("Hello, World 7!");string.Length는 UTF-16 코드 단위 수라 보조 평면에 있는 이모지 하나가 2로 세어집니다. 사용자에게 보이는 글자 수를 세려면 StringInfo를 써야 합니다.
출력을 파일로 리디렉션하면 콘솔 인코딩이 아니라 스트림 인코딩이 적용되므로, Console.OutputEncoding만 바꿔 두고 안심하면 저장된 파일이 깨질 수 있습니다.
08환경 변수 읽기
Environment.GetEnvironmentVariable로 값을 조회하고 없으면 기본 문자열을 내보냅니다. 실행 환경을 구분해 동작을 바꿀 때 쓰는 기본 형태입니다. 변수 이름 대소문자는 윈도우에서 구분되지 않지만 리눅스와 macOS에서는 구분되므로, 같은 코드가 플랫폼에 따라 다르게 동작할 수 있습니다.
C# code
// 환경 변수 읽기
using System;
Console.WriteLine(Environment.GetEnvironmentVariable("APP_ENV") ?? $"run-8");Environment.GetEnvironmentVariables()로 전체 목록을 얻을 수 있지만 여기에는 토큰이나 비밀번호가 섞여 있을 수 있어, 그대로 로그에 남기면 안 됩니다.
환경 변수 값은 언제나 문자열이라 false나 0도 비어 있지 않은 값입니다. 존재 여부만 보고 기능을 켜면, 끄려고 넣은 값이 오히려 켜는 결과가 됩니다.
09컬렉션 요약 출력
컬렉션은 없고 문자열 한 줄을 출력할 뿐입니다. 요약을 찍으려면 개수나 합계를 계산해 문자열에 넣는 단계가 필요합니다. 배열을 Console.WriteLine에 그대로 넘기면 내용이 아니라 타입 이름이 출력되므로, 원소를 보려면 string.Join으로 직접 이어 붙여야 합니다.
C# code
// 컬렉션 요약 출력
using System;
Console.WriteLine("Hello, World 9!");string.Join은 원소마다 ToString을 부르므로, 직접 만든 타입이라면 ToString을 재정의해 두는 것만으로 읽을 만한 요약이 나옵니다.
요약을 만들 때 Count와 Sum을 따로 부르면 시퀀스를 두 번 훑습니다. 원본이 지연 실행이라면 계산이나 질의도 두 번 일어납니다.
10버전 정보 출력
버전 정보를 읽는 코드는 없고 환경 변수 값이나 대체 문자열을 출력합니다. 실행 정보를 찍는 자리에 어울리는 형태입니다. Environment.Version은 실행 중인 런타임 버전을 알려 줄 뿐 내 프로그램의 버전이 아니므로, 앱 버전은 어셈블리 정보에서 따로 가져와야 합니다.
C# code
// 버전 정보 출력
using System;
Console.WriteLine(Environment.GetEnvironmentVariable("APP_ENV") ?? $"run-10");실행 파일의 버전은 진입 어셈블리에서 얻을 수 있고, 빌드에 심어 둔 표시용 버전 문자열은 정보 버전 특성으로 따로 읽어야 합니다.
단일 파일로 게시하면 어셈블리 위치나 파일 경로에 의존하는 버전 조회가 빈 값을 돌려줄 수 있으므로, 배포 형태를 바꿨다면 실제로 값이 찍히는지 확인해야 합니다.
정리하며
- 최상위 문을 써도 컴파일러가 진입점 클래스를 생성하는 구조 자체는 달라지지 않습니다
- 문자열 조립은
+연결보다 보간 문자열을 기본 선택지로 잡아 두는 편이 낫습니다 args[0]은 실행 파일 경로가 아니라 사용자가 넘긴 첫 번째 인자입니다- 콘솔에서 한글이 깨지면
Console.OutputEncoding을 UTF-8로 맞춰 해결합니다
더 깊이 들어가고 싶다면 C# 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.