Visual Studio 콘솔앱 만들기, 파일 나누기와 디버깅
목차
Visual Studio에서 콘솔 앱을 조금 더 제대로 다루는 방법을 정리합니다. 솔루션과 프로젝트가 어떻게 생겼는지 보고, 클래스를 여러 파일로 나눈 할 일 관리 프로그램을 만듭니다. 그다음 빌드한 실행 파일이 어디에 생기는지 확인하고, 중단점을 걸어 버그를 찾는 디버깅까지 해 봅니다.
이 글은 C# 고급 문법, 클래스부터 LINQ까지에서 이어집니다. 지난 글에서는 클래스를 Program.cs 한 파일 아래쪽에 함께 썼습니다. 이번 글에서는 그 클래스를 각자의 파일로 나눕니다.
Visual Studio 화면은 버전에 따라 다르게 보일 수 있습니다. 이 글의 그림은 공식 문서를 바탕으로 그린 구조도이고, 실제 화면을 찍은 것이 아닙니다. 메뉴 이름은 공식 한국어 문서에서 확인한 이름 뒤에 영어 이름을 괄호로 적었습니다. 한국어 문서끼리도 번역이 다른 메뉴는 단축키와 영어 이름을 먼저 쓰고, 한국어 이름에는 "한국어 표시는 버전에 따라 다를 수 있음"이라고 붙였습니다.
실행 결과와 빌드 메시지는 .NET 10 SDK로 직접 빌드하고 실행해서 얻은 것입니다. 디버거 표의 값은 Visual Studio 디버거로 같은 위치에 멈췄을 때 보이는 값입니다. 이 글의 예제는 입력을 받지 않고 정해진 데이터로만 동작합니다.
1. 솔루션과 프로젝트

첫 글에서 콘솔 앱 프로젝트를 만들 때 솔루션 파일도 함께 생긴다고만 말했습니다. 이번에는 솔루션과 프로젝트가 무엇이고 폴더에 어떻게 저장되는지 봅니다.
이 글의 예제를 따라 하려면 첫 글과 같은 방법으로 콘솔 앱(Console App) 프로젝트를 새로 만듭니다. 프로젝트 이름은 TodoApp, 프레임워크는 .NET 10.0으로 고릅니다.
솔루션, 프로젝트, 소스 파일
세 가지는 큰 상자 안에 작은 상자가 들어 있는 관계입니다.
| 이름 | 파일 | 하는 일 |
|---|---|---|
| 솔루션(solution) | TodoApp.slnx 또는 TodoApp.sln |
프로젝트 여러 개를 묶는 작업 공간 |
| 프로젝트(project) | TodoApp.csproj |
프로그램 하나(실행 파일이나 DLL 하나)를 만드는 단위 |
| 소스 파일 | Program.cs, TodoItem.cs 등 |
우리가 코드를 쓰는 파일 |
지금은 솔루션 하나에 프로젝트가 하나뿐이라 둘의 차이가 잘 느껴지지 않습니다. 나중에 화면 프로그램과 DLL 프로젝트를 한 솔루션에 함께 넣게 되면 솔루션이 왜 필요한지 보입니다.
폴더 구조
Visual Studio는 기본으로 %USERPROFILE%\source\repos 아래에 프로젝트를 저장합니다. %USERPROFILE%은 보통 C:\Users\사용자이름 폴더입니다. 저장 위치는 도구 > 옵션 > 프로젝트 및 솔루션 > 위치(Tools > Options > Projects and Solutions > Locations)에서 바꿀 수 있습니다.
아래는 공식 문서를 바탕으로 정리한 폴더 구조 예시입니다. 실행 결과가 아니라 구조를 설명하려고 쓴 그림입니다. 새 프로젝트 구성 단계의 Place solution and project in the same directory 체크 상자를 체크하지 않았을 때의 모양입니다.
C:\Users\사용자이름\source\repos\
└─ TodoApp\ ← 솔루션 폴더
├─ TodoApp.slnx ← 솔루션 파일 (.sln일 수도 있음)
├─ .vs\ ← 숨김 폴더 (개인 설정)
└─ TodoApp\ ← 프로젝트 폴더
├─ TodoApp.csproj ← 프로젝트 파일
├─ Program.cs
├─ TodoItem.cs
├─ TodoList.cs
├─ bin\ ← 빌드 결과
└─ obj\ ← 빌드 중간 파일
이 체크 상자를 체크하면 솔루션 파일과 프로젝트 파일이 한 폴더에 들어가서 폴더가 한 단계 줄어듭니다. 이 체크 상자의 한국어 표시와 기본 체크 여부는 환경에 따라 다를 수 있습니다. 그래서 여러분의 폴더는 위 그림과 단계가 다를 수 있습니다.
.vs 폴더는 숨김 폴더입니다. 열어 둔 파일, 중단점 같은 개인 설정이 여기에 저장됩니다. bin과 obj는 빌드할 때마다 다시 만들어지는 폴더입니다. 그래서 프로젝트를 다른 사람에게 줄 때는 .vs, bin, obj를 빼도 됩니다.
솔루션 파일 .slnx와 .sln
솔루션 파일에는 "이 솔루션에 어떤 프로젝트가 들어 있는지"가 적혀 있습니다. 옛 형식은 .sln이고, 새 형식은 XML로 된 .slnx입니다. 아래는 같은 구조를 명령줄의 dotnet new sln으로 만들고 프로젝트를 추가해서 얻은 TodoApp.slnx의 실제 내용입니다.
<Solution>
<Project Path="TodoApp/TodoApp.csproj" />
</Solution>
내용은 프로젝트 파일의 위치 한 줄뿐입니다. 명령줄의 .NET 10 SDK는 .slnx를 기본으로 만들었습니다. Visual Studio에서는 도구 > 옵션 > 프로젝트 및 솔루션 > 일반(Tools > Options > Projects and Solutions > General)에서 새 솔루션의 파일 형식을 고를 수 있습니다. 설치 직후의 기본값은 환경에 따라 다를 수 있습니다. 여러분의 폴더에 .sln 파일이 있어도 이 글을 따라 하는 데는 차이가 없습니다.
프로젝트 파일 .csproj
프로젝트 파일에는 이 프로그램을 어떻게 만들지가 적혀 있습니다. 콘솔 앱 템플릿으로 만든 TodoApp.csproj는 아래와 같습니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
OutputType이Exe라서 실행 파일을 만듭니다.TargetFramework의net10.0이 .NET 10을 쓴다는 뜻입니다. 이 값은 뒤에서 볼 출력 폴더 이름에도 들어갑니다.ImplicitUsings는System같은 자주 쓰는using을 자동으로 넣어 주는 설정입니다. 그래서 지금까지using System;을 쓰지 않아도Console을 쓸 수 있었습니다.Nullable은 지난 글들에서 본string?처럼 null이 될 수 있는지를 컴파일러가 검사하게 하는 설정입니다.
솔루션 탐색기
솔루션 탐색기(Solution Explorer)는 솔루션과 프로젝트를 나무 모양으로 보여 주는 창입니다. 창이 안 보이면 보기 > 솔루션 탐색기(View > Solution Explorer)를 누르거나 Ctrl+Alt+L을 누릅니다.
맨 위에 솔루션, 그 아래에 프로젝트, 그 아래에 종속성(Dependencies)과 .cs 파일이 보입니다. 프로젝트를 오른쪽 클릭하면 아래 메뉴를 자주 씁니다.
| 메뉴 | 하는 일 |
|---|---|
| 프로젝트 파일 편집(Edit Project File) | .csproj 내용을 편집기로 엽니다 |
| 모든 파일 표시(Show All Files) | 숨겨진 bin, obj 폴더까지 보여 줍니다 |
| 파일 탐색기에서 폴더 열기(Open Folder in File Explorer) | 실제 프로젝트 폴더를 Windows 파일 탐색기로 엽니다 |
| 시작 프로젝트로 설정(Set as Startup Project) | 프로젝트가 여러 개일 때 F5로 실행할 프로젝트를 고릅니다 |
헷갈리기 쉬운 점
- 솔루션과 프로젝트는 다른 것입니다. 솔루션은 묶는 상자이고, 프로젝트는 프로그램 하나입니다. 처음에는 둘 다 이름이
TodoApp이라서 폴더도TodoApp안에TodoApp이 있는 모양이 됩니다. - bin과 obj가 솔루션 탐색기에 안 보이는 것은 정상입니다. 기본으로 숨겨져 있고, 모든 파일 표시를 누르면 보입니다.
- .vs 폴더는 지워도 프로젝트가 망가지지 않습니다. 대신 중단점 같은 개인 설정이 사라집니다. 숨김 폴더라서 파일 탐색기에서 숨김 항목 보기를 켜야 보입니다.
- 솔루션 파일을 직접 고칠 일은 거의 없습니다. 프로젝트 추가와 제거는 솔루션 탐색기에서 하면 Visual Studio가 알아서 적어 줍니다.
2. 클래스를 파일로 나누기와 namespace

지난 글까지는 클래스를 Program.cs 한 파일에 함께 썼습니다. 클래스가 늘어나면 한 파일이 너무 길어집니다. 그래서 보통은 클래스 하나를 파일 하나에 두고, 파일 이름을 클래스 이름과 같게 짓습니다.
새 파일 만들기
할 일 하나를 나타내는 TodoItem 클래스를 새 파일로 만들어 봅니다.
- 솔루션 탐색기에서 프로젝트 이름(
TodoApp)을 오른쪽 클릭합니다. - 추가 > 새 항목(Add > New Item)을 누릅니다. 단축키는 Ctrl+Shift+A입니다.
- 빠른 추가(Quick Add) 창이 열리면
TodoItem.cs를 입력하고 Enter를 누릅니다. - 빠른 추가 대신 전체 템플릿 목록이 필요하면 모든 템플릿 표시(Show All Templates)를 누르고 클래스(Class)를 고릅니다.
- 새로 만들어진 파일의 내용을 아래 예제 코드로 바꿉니다.
새 파일에 처음 만들어지는 코드의 모양은 Visual Studio 버전과 설정에 따라 다를 수 있습니다. 그래서 이 글에서는 만들어진 내용을 지우고 예제 코드로 바꿔 넣습니다. 같은 방법으로 TodoList.cs도 만듭니다.
예제: 파일 세 개로 나눈 할 일 관리 프로그램
첫 번째 파일 TodoItem.cs입니다. 할 일 하나의 번호, 제목, 완료 여부를 담습니다.
namespace TodoApp;
// 할 일 하나를 나타내는 클래스 (파일 이름 = 클래스 이름)
public class TodoItem
{
public int Id { get; }
public string Title { get; }
public bool IsDone { get; private set; }
public TodoItem(int id, string title)
{
Id = id;
Title = title;
}
// 완료 표시
public void Complete()
{
IsDone = true;
}
// Console.WriteLine(item)을 하면 이 모양으로 출력됩니다
public override string ToString()
{
string mark = IsDone ? "[v]" : "[ ]";
return $"{mark} {Id}. {Title}";
}
}
IsDone의private set은 클래스 바깥에서는 읽기만 하고, 값은Complete메서드로만 바꾸게 하는 속성입니다. 지난 글에서 본 속성의get,set을 쓰는 방법 중 하나입니다.ToString을override하면Console.WriteLine(item)처럼 객체를 바로 출력할 때 이 모양이 나옵니다. 지난 글에서 본override를object의 메서드에 쓴 것입니다.
두 번째 파일 TodoList.cs입니다. 할 일 여러 개를 List로 관리합니다.
namespace TodoApp;
// 할 일 여러 개를 관리하는 클래스
public class TodoList
{
private readonly List<TodoItem> _items = new List<TodoItem>();
private int _nextId = 1;
public int Count => _items.Count;
public int DoneCount => _items.Count(item => item.IsDone);
// 새 할 일 추가 (번호는 1부터 자동으로 붙습니다)
public TodoItem Add(string title)
{
TodoItem item = new TodoItem(_nextId, title);
_nextId++;
_items.Add(item);
return item;
}
// 번호로 찾아서 완료 처리. 없는 번호면 false
public bool Complete(int id)
{
TodoItem? item = _items.Find(x => x.Id == id);
if (item == null)
{
return false;
}
item.Complete();
return true;
}
public void PrintAll()
{
foreach (TodoItem item in _items)
{
Console.WriteLine(item);
}
Console.WriteLine($"완료 {DoneCount} / 전체 {Count}");
}
}
DoneCount는 지난 글의 LINQCount에 람다로 조건을 준 것입니다. 완료된 할 일만 셉니다.Find는 조건에 맞는 첫 항목을 돌려줍니다. 맞는 항목이 없으면 null을 돌려주므로 변수 타입을TodoItem?으로 썼습니다.
세 번째 파일 Program.cs입니다. 실행할 문장만 있습니다.
// 예제: 클래스를 파일로 나눈 할 일 관리 프로그램 (입력 없이 고정 데이터)
using TodoApp; // TodoItem.cs, TodoList.cs의 namespace
TodoList todos = new TodoList();
todos.Add("C# 공부하기");
todos.Add("중단점 찍어 보기");
todos.Add("Release로 빌드해 보기");
Console.WriteLine("=== 처음 목록 ===");
todos.PrintAll();
todos.Complete(1);
todos.Complete(3);
bool ok = todos.Complete(9); // 없는 번호
Console.WriteLine($"9번 완료 처리 결과: {ok}");
Console.WriteLine("=== 완료 처리 후 ===");
todos.PrintAll();
실행 결과입니다.
=== 처음 목록 ===
[ ] 1. C# 공부하기
[ ] 2. 중단점 찍어 보기
[ ] 3. Release로 빌드해 보기
완료 0 / 전체 3
9번 완료 처리 결과: False
=== 완료 처리 후 ===
[v] 1. C# 공부하기
[ ] 2. 중단점 찍어 보기
[v] 3. Release로 빌드해 보기
완료 2 / 전체 3
없는 번호인 9번을 완료 처리하면 False가 나오고, 목록은 그대로입니다. 프로그램은 세 파일로 나뉘었지만, 빌드하면 하나의 프로그램이 됩니다.
namespace는 이름을 묶는 상자
TodoItem.cs와 TodoList.cs의 첫 줄 namespace TodoApp;은 "이 파일의 클래스는 TodoApp이라는 이름 묶음에 들어간다"는 뜻입니다. namespace 이름은 보통 프로젝트 이름을 따라갑니다. 프로젝트의 기본 namespace(RootNamespace)가 프로젝트 이름과 같기 때문입니다.
namespace를 쓰는 방법은 두 가지입니다. 아래 두 코드는 같은 뜻입니다.
// 파일 범위 namespace: 이 파일 전체가 TodoApp에 들어갑니다
namespace TodoApp;
public class TodoItem
{
}
// 블록 namespace: 중괄호 안만 TodoApp에 들어갑니다
namespace TodoApp
{
public class TodoItem
{
}
}
첫 번째 모양은 들여쓰기가 한 단계 줄어서 요즘 예제에 많이 쓰입니다. 한 파일에는 둘 중 한 가지만 씁니다.
using이 필요한 이유
Program.cs의 using TodoApp;은 "TodoApp namespace에 있는 클래스를 이름만으로 쓰겠다"는 뜻입니다. Program.cs처럼 최상위 문으로 쓴 코드는 어떤 namespace에도 속하지 않는 전역 namespace에 들어갑니다. 공식 문서도 최상위 문은 암시적으로 전역 namespace에 있다고 설명합니다. 그래서 TodoApp namespace의 클래스를 쓰려면 using이 필요합니다.
using TodoApp; 줄을 지우고 빌드하면 아래 오류가 납니다. 경로는 줄여서 적었습니다.
Program.cs(3,1): error CS0246: The type or namespace name 'TodoList' could not be found (are you missing a using directive or an assembly reference?)
Program.cs(3,22): error CS0246: The type or namespace name 'TodoList' could not be found (are you missing a using directive or an assembly reference?)
TodoList라는 이름을 찾을 수 없다는 오류 CS0246입니다. 3번째 줄의 TodoList가 두 번 나와서(1번째 칸과 22번째 칸) 오류도 두 줄입니다. 괄호 안의 메시지대로 using 지시문이 빠졌는지 먼저 확인합니다. 한국어 Visual Studio에서는 같은 오류가 한국어 메시지로 보일 수 있습니다.
헷갈리기 쉬운 점
- 파일 이름과 클래스 이름이 같아야 하는 것은 아닙니다. 컴파일러가 강제하지 않는 관례입니다. 하지만 클래스를 찾기 쉽도록 같게 짓습니다.
- 같은 클래스를 두 파일에 쓰면 빌드 오류가 납니다.
TodoItem.cs를 복사해서TodoItemCopy.cs를 만들면, 같은 namespace에TodoItem이 두 개가 되어 아래 오류가 납니다. 파일을 복사해서 실험할 때는 클래스 이름도 바꿔야 합니다.
TodoItemCopy.cs(4,14): error CS0101: The namespace 'TodoApp' already contains a definition for 'TodoItem'
TodoItemCopy.cs(10,12): error CS0111: Type 'TodoItem' already defines a member called 'TodoItem' with the same parameter types
TodoItemCopy.cs(17,17): error CS0111: Type 'TodoItem' already defines a member called 'Complete' with the same parameter types
TodoItemCopy.cs(23,28): error CS0111: Type 'TodoItem' already defines a member called 'ToString' with the same parameter types
- 첫 줄의 CS0101이 "
TodoAppnamespace에 이미TodoItem이 있다"는 뜻입니다. 나머지 CS0111 줄은 생성자,Complete,ToString도 각각 두 번 정의됐다는 오류입니다. 첫 오류를 고치면 함께 사라집니다. - namespace 줄을 지운다고 using 문제가 해결되는 것은 아닙니다. 예제를 오래 쓰려면 클래스 파일에는 namespace를 두고, 쓰는 쪽에서
using을 적는 습관이 좋습니다. - 처음에는 하위 폴더를 만들지 않습니다. 프로젝트 안에 폴더를 만들어 파일을 옮기면 namespace를 폴더에 맞춰 바꿀지 고민할 거리가 생깁니다. 이 시리즈의 콘솔 예제는 파일을 프로젝트 바로 아래에 둡니다.
3. 최상위 문과 Main
![최상위 문과 Main 비교 그림(같은 프로그램, 두 가지 모양). 모양 1 최상위 문: Program.cs에 using TodoApp;, new TodoList(), todos.Add('중단점 찍어 보기'), todos.PrintAll()만 있고 '실행할 문장만 씀, 이 파일이 시작점' 메모. 모양 2 Main 메서드: namespace TodoMain; 안에 internal class Program, 그 안에 static void Main(string[] args)와 실행 결과(첫 줄 생략) [v] 2. 중단점 찍어 보기, [ ] 3. Release로 빌드해 보기, 완료 1 / 전체 3, args 개수: 0. '최상위 문도 속은 같은 모양' 부분은 컴파일러가 class Program과 static void <Main>$(string[] args)를 대신 만들고 모양 1의 문장들이 그 안에 들어간다는 개념도(실제 생성 코드와 세부는 다를 수 있음, 예외 메시지에 Program.<Main>$가 보이면 최상위 문 코드라는 뜻). 비교 표: namespace는 전역(using 필요)과 TodoMain 안(using 불필요), args는 그냥 args(자동으로 생김)와 매개변수 string[] args, 진입점은 Program.<Main>$와 Program.Main, 쓰는 곳은 지금까지 예제와 Windows Forms 템플릿. 아래 '최상위 문은 프로젝트에 한 파일만': 두 파일에 쓰면 빌드 오류 error CS8802(Only one compilation unit can have top-level statements), Main과 최상위 문이 함께 있으면 경고 warning CS7022(ignoring 'Program.Main(string[])' entry point), 경고만 나오고 최상위 문이 실행됨. 초보가 Main이 없는데 어떻게 시작하냐고 묻고, 선배가 컴파일러가 진입점을 만들어 준다고 답함](https://blog.kakaocdn.net/dna/QmHRG/dJMcagtIsgB/AAAAAAAAAAAAAAAAAAAAAIqyME7E1LrTgEy27E8mW8mliHmmxXVM1Q8_AiZTeX4F/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1796050799&allow_ip=&allow_referer=&signature=HLFFsfg8z7oercyjmpjb8kAf6Bo%3D)
첫 글에서 콘솔 앱 코드에는 최상위 문 모양과 Main 메서드 모양이 있다고 짧게 봤습니다. 이번에는 파일이 여러 개인 프로젝트에서 두 모양을 비교합니다.
Main 메서드 모양
새 프로젝트를 만들 때 최상위 문 사용 안 함(Do not use top-level statements)에 체크하면 Program.cs가 Main 메서드 모양으로 만들어집니다. 앞의 할 일 프로그램을 이 모양으로 쓰면 아래와 같습니다. TodoItem.cs와 TodoList.cs는 첫 줄이 namespace TodoMain;인 것만 다르고 나머지는 앞과 같습니다.
// 앞의 TodoApp과 같은 프로그램을 "최상위 문 사용 안 함" 모양(Main 메서드)으로 쓴 것
namespace TodoMain;
internal class Program
{
static void Main(string[] args)
{
// 같은 namespace(TodoMain) 안이라 using TodoMain; 이 필요 없습니다
TodoList todos = new TodoList();
todos.Add("C# 공부하기");
todos.Add("중단점 찍어 보기");
todos.Add("Release로 빌드해 보기");
todos.Complete(2);
todos.PrintAll();
Console.WriteLine($"args 개수: {args.Length}");
}
}
실행 결과입니다.
[ ] 1. C# 공부하기
[v] 2. 중단점 찍어 보기
[ ] 3. Release로 빌드해 보기
완료 1 / 전체 3
args 개수: 0
Program클래스도namespace TodoMain;안에 있습니다. 같은 namespace 안의 클래스끼리는using없이 서로 쓸 수 있습니다.static void Main(string[] args)가 프로그램의 진입점(entry point)입니다. 프로그램을 실행하면 이 메서드부터 시작합니다.args는 명령줄에서 프로그램을 실행할 때 뒤에 붙여 넘긴 값들입니다. 아무것도 넘기지 않았으므로 개수가 0입니다.- 클래스 앞의
internal은 이 프로젝트 안에서만 쓰는 클래스라는 뜻입니다.
최상위 문도 속은 같은 모양
최상위 문으로 쓰면 Program 클래스와 진입점 메서드를 컴파일러가 대신 만들어 줍니다. 우리가 쓴 문장은 그 메서드 안에 들어갑니다. 그래서 두 모양은 쓰는 방법만 다르고, 만들어지는 프로그램은 같은 구조입니다.
컴파일러가 만든 진입점은 Program.<Main>$라는 이름으로 보입니다. 지난 글들의 예외 메시지에서 이 이름을 본 적이 있습니다. 이 글의 디버깅 예제를 디버거로 멈췄을 때도 현재 위치가 Program.<Main>$()로 표시됐습니다. 예외 메시지나 호출 스택에서 이 이름이 보이면 최상위 문에 쓴 코드라는 뜻입니다.
| 비교 | 최상위 문 | Main 메서드 |
|---|---|---|
| namespace | 전역 namespace라서 다른 namespace의 클래스는 using 필요 |
직접 쓴 namespace 안이라 같은 namespace면 using 불필요 |
| args | 선언 없이 args를 바로 쓸 수 있음 |
string[] args 매개변수로 받음 |
| 진입점 이름 | Program.<Main>$ (컴파일러가 만듦) |
Program.Main |
| 주로 쓰는 곳 | 지금까지의 예제, 짧은 콘솔 앱 | Windows Forms 같은 템플릿 |
어느 쪽으로 써도 됩니다. 이 시리즈의 콘솔 예제는 계속 최상위 문을 씁니다. 나중에 Windows Forms 프로젝트를 만들면 Program.cs가 Main 메서드 모양으로 만들어지니, 그때 이 표를 다시 보면 됩니다.
헷갈리기 쉬운 점
- 최상위 문은 프로젝트에서 한 파일에만 쓸 수 있습니다. 진입점은 하나여야 하기 때문입니다. 할 일 프로젝트에
Hello.cs를 만들고Console.WriteLine("안녕하세요");한 줄만 써서 빌드하면 아래 오류가 납니다.
Program.cs(4,1): error CS8802: Only one compilation unit can have top-level statements.
- 이 빌드에서는 오류 위치가 새로 만든
Hello.cs가 아니라Program.cs의 첫 문장(4번째 줄)으로 표시됐습니다. 오류에 나온 파일만 보지 말고, 클래스 없이 문장만 있는 파일이 둘인지 찾아봅니다. - 새 파일에 클래스 없이 문장부터 쓰면 그 파일도 최상위 문이 됩니다. 새 파일에는 클래스를 쓰고, 실행할 문장은
Program.cs에만 씁니다. - Main이 있는 프로젝트에 최상위 문 파일이 생기면 오류가 아니라 경고가 납니다.
Main모양 프로젝트에Console.WriteLine("최상위 문이 실행됐습니다.");한 줄짜리Hello.cs를 넣고 빌드하면 아래 경고가 나옵니다.
Program.cs(6,17): warning CS7022: The entry point of the program is global code; ignoring 'Program.Main(string[])' entry point.
- 경고만 나오고 빌드는 성공합니다. 그런데 실행하면
Main은 무시되고 최상위 문만 실행돼서 아래 한 줄만 출력됩니다. 할 일 목록은 하나도 나오지 않습니다. 경고 목록도 꼭 읽어야 하는 이유입니다.
최상위 문이 실행됐습니다.
- 최상위 문 사용 안 함 체크 상자는 만들 때만 고릅니다. 이미 만든 프로젝트는
Program.cs를 직접 고쳐서 모양을 바꿉니다.
4. 빌드와 실행 파일 위치

빌드(build)는 우리가 쓴 .cs 파일들을 컴퓨터가 실행할 수 있는 파일로 만드는 작업입니다. 지금까지는 Ctrl+F5를 누를 때 Visual Studio가 알아서 빌드했습니다. 이번에는 빌드를 따로 해 보고, 결과 파일이 어디에 생기는지 봅니다.
빌드 메뉴
| 메뉴 | 단축키 | 하는 일 |
|---|---|---|
| 빌드 > 솔루션 빌드(Build Solution) | Ctrl+Shift+B | 바뀐 파일만 다시 컴파일합니다 |
| 빌드 > 솔루션 다시 빌드(Rebuild Solution) | 없음 | 결과를 전부 지우고 처음부터 다시 빌드합니다 |
| 빌드 > Clean Solution | 없음 | 빌드 결과를 지웁니다 |
Clean Solution은 한국어 문서에도 영어로 남아 있습니다. 한국어 화면에서는 "솔루션 정리"처럼 보일 수 있지만, 한국어 표시는 버전에 따라 다를 수 있습니다.
빌드 결과는 출력(Output) 창에, 오류와 경고는 오류 목록(Error List) 창에 나옵니다. F5나 Ctrl+F5로 실행하면 실행 전에 자동으로 빌드하므로 평소에는 빌드 메뉴를 따로 누르지 않아도 됩니다.
빌드 결과가 생기는 곳: bin\Debug\net10.0
빌드 결과는 프로젝트 폴더 안의 bin\Debug\net10.0 폴더에 생깁니다. Debug는 빌드 구성 이름이고, net10.0은 .csproj의 TargetFramework 값입니다. 빌드 구성은 마지막 장에서 봅니다.
실제로 어떤 파일이 생기는지 확인하려고, 실행 중인 프로그램이 자기 폴더의 파일 목록을 출력하는 예제를 만들었습니다. BuildInfo라는 콘솔 앱 프로젝트의 Program.cs입니다.
// 빌드 구성(Debug/Release)과 실행 파일 위치 확인하기
using System.Reflection;
// 1) 지금 실행 중인 프로그램이 어떤 구성으로 빌드됐는지
string? config = Assembly.GetExecutingAssembly()
.GetCustomAttribute<AssemblyConfigurationAttribute>()?.Configuration;
Console.WriteLine($"빌드 구성: {config}");
#if DEBUG
Console.WriteLine("이 줄은 Debug 빌드에서만 보입니다. (#if DEBUG)");
#else
Console.WriteLine("이 줄은 Release 빌드에서만 보입니다. (#else)");
#endif
// 2) 실행 파일이 있는 폴더 (전체 경로 대신 마지막 세 단계만)
string baseDir = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar);
string[] parts = baseDir.Split(Path.DirectorySeparatorChar);
string lastThree = string.Join("\\", parts[^3..]);
Console.WriteLine($"실행 폴더: ...\\{lastThree}\\");
// 3) 그 폴더에 있는 파일 목록
Console.WriteLine("폴더 안의 파일:");
foreach (string file in Directory.GetFiles(baseDir).Order())
{
Console.WriteLine(" " + Path.GetFileName(file));
}
1번 부분은 프로그램 자기 자신의 정보를 읽는 코드입니다. 지금은 "어떤 구성으로 빌드됐는지 알아내는 코드" 정도로만 보면 됩니다. #if DEBUG는 마지막 장에서 설명합니다. 2번 부분은 전체 경로가 사람마다 다르기 때문에 뒤에서 세 단계(parts[^3..])만 잘라서 출력합니다.
Debug로 빌드해서 실행한 결과입니다.
빌드 구성: Debug
이 줄은 Debug 빌드에서만 보입니다. (#if DEBUG)
실행 폴더: ...\bin\Debug\net10.0\
폴더 안의 파일:
BuildInfo.deps.json
BuildInfo.dll
BuildInfo.exe
BuildInfo.pdb
BuildInfo.runtimeconfig.json
실행 폴더가 bin\Debug\net10.0이고, 그 안에 실행 파일 BuildInfo.exe와 우리 코드가 든 BuildInfo.dll이 같은 폴더에 있습니다.
bin\Debug\net10.0에 생긴 파일과 크기는 아래와 같습니다. 파일 크기(특히 .pdb)는 SDK·경로에 따라 달라질 수 있습니다.
| 파일 | 크기 | 하는 일 |
|---|---|---|
BuildInfo.dll |
5,632바이트 | 우리가 쓴 코드가 컴파일되어 들어 있는 파일 |
BuildInfo.exe |
162,304바이트 | 같은 폴더의 dll을 실행해 주는 Windows 실행 파일 |
BuildInfo.pdb |
11,112바이트 | 디버거가 코드 줄과 변수 이름을 찾을 때 쓰는 디버그 정보 |
BuildInfo.deps.json |
421바이트 | 이 프로그램이 의존하는 것들의 목록 |
BuildInfo.runtimeconfig.json |
270바이트 | .NET 10 런타임으로 실행하라는 설정 |
.exe가 .dll보다 훨씬 큰 것이 눈에 띕니다. 우리 코드는 .dll에 들어 있고, .exe는 그 dll을 실행해 주는 역할을 합니다. 이 실행 파일은 .NET 런타임이 설치된 PC에서 실행됩니다. 명령줄에서 dotnet BuildInfo.dll로 dll을 직접 실행할 수도 있습니다.
실행 파일 직접 실행하기
Visual Studio 없이 실행 파일을 실행해 봅니다. 할 일 프로젝트라면 아래 순서입니다.
- 솔루션 탐색기에서 프로젝트를 오른쪽 클릭하고 파일 탐색기에서 폴더 열기(Open Folder in File Explorer)를 누릅니다.
- 열린 폴더에서
bin\Debug\net10.0폴더로 들어갑니다. - 그 폴더에서 터미널(PowerShell이나 명령 프롬프트)을 엽니다. 파일 탐색기의 빈 곳을 오른쪽 클릭하면 터미널을 여는 메뉴가 있을 수 있지만, 메뉴 이름은 Windows 버전에 따라 다를 수 있습니다.
.\TodoApp.exe를 입력하고 Enter를 누릅니다.
출력되는 내용은 앞에서 본 할 일 프로그램의 실행 결과와 같습니다.
TodoApp.exe를 파일 탐색기에서 더블클릭해도 실행은 됩니다. 다만 콘솔 창이 프로그램이 끝나자마자 닫혀서 결과를 보기 전에 사라질 수 있습니다. 결과를 보려면 위처럼 터미널에서 실행하는 것이 확실합니다.
헷갈리기 쉬운 점
- exe 하나만 떼어 내면 안 됩니다.
.exe는 같은 폴더의.dll을 실행해 주는 파일입니다. 폴더의 파일들이 함께 있어야 합니다. 다른 사람에게 줄 실행 파일은 게시(Publish) 기능으로 따로 만드는데, 이 내용은 이 시리즈의 뒤쪽에서 다룹니다. - 실행하는 PC에 .NET 런타임이 있어야 합니다. 이 방식의 실행 파일은 .NET 런타임이 설치된 PC를 전제로 합니다. Visual Studio를 설치한 PC에는 이미 들어 있습니다.
- bin과 obj를 지워도 괜찮습니다. 다음 빌드 때 다시 생깁니다. 빌드가 이상하게 꼬였을 때 솔루션 다시 빌드를 하는 것과 비슷한 효과입니다.
5. 중단점과 한 줄씩 실행
![중단점과 한 줄씩 실행 그림(디버거로 평균 버그 두 개 잡기). 맨 위 '평균이 이상하다' 결과 상자: 점수: 90, 85, 77, 68, 94, 평균: 64(X 표시), 기대한 평균: 82.8. '11행에 중단점 → F5로 실행' 코드 조각: int sum = 0;(8행), for (int i = 1; ...), sum += scores[i];(11행, 빨간 중단점과 노란 화살표), return sum / scores.Length;(13행), 그리고 F9 중단점 설정/해제 → 빨간 점, F5 디버깅 시작 → 중단점에서 멈춤, Shift+F5 디버깅 중지 키 설명. 로봇이 노란 화살표 줄은 아직 실행 전인 줄이라고 말함. '멈출 때마다 값 보기(Locals, Alt+4)' 표: 11행 1번째 i 1, sum 0, scores[i] 85(빨간 강조) / 2번째 2, 85, 77 / 3번째 3, 162, 68 / 4번째 4, 230, 94 / 13행 return sum 324, 메모 '첫 멈춤에서 i가 이미 1 → 90을 건너뜀, 반복은 4번뿐, 끝나면 sum = 324'. '버그 2개 고치기': 버그 1 0번(90)을 빼먹음은 for (int i = 1; ...)을 for (int i = 0; ...)으로, 버그 2 324 / 5 → 64(정수 나눗셈)는 return sum / scores.Length;를 return (double)sum / scores.Length;로 고침, 고친 뒤 실행 결과 평균: 82.8. 맨 아래 '한 줄씩 실행하는 키'(Program.cs 7행에서 멈춘 뒤 눌러 본 결과): F11 Step Into → 메서드 안으로, ScoreCalculator.cs로 들어감 / F10 Step Over → 들어가지 않고 다음 줄, Program.cs 8행, average = 64 / Shift+F11 Step Out → 메서드를 끝내고 부른 곳으로, Program.cs 7행으로 돌아옴, 이 세 메뉴의 한국어 표기는 버전마다 달라서 키와 영어 이름으로 적었다는 메모](https://blog.kakaocdn.net/dna/d3AI99/dJMcaa8fnzj/AAAAAAAAAAAAAAAAAAAAAGxNHvNLSMvFh01eWBvkpWH5UXf-Pq9IHYG-qOoonpwS/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1796050799&allow_ip=&allow_referer=&signature=QYoHAjIg%2BQpbT1cZnNmCDJDN%2BO0%3D)
디버깅(debugging)은 프로그램의 버그를 찾아 고치는 일입니다. Visual Studio의 디버거를 쓰면 프로그램을 원하는 줄에서 멈추고, 그 순간의 변수 값을 보고, 한 줄씩 실행할 수 있습니다.
연습용 프로그램: 평균이 이상하다
점수 다섯 개의 평균을 구하는 프로그램입니다. BugHunt라는 콘솔 앱 프로젝트에 파일 두 개를 만듭니다. 이 코드에는 일부러 버그 두 개를 넣었습니다.
ScoreCalculator.cs입니다. 줄 번호를 디버깅에서 쓰므로, 파일 첫 줄 namespace BugHunt;부터 세어서 11번째 줄이 sum += scores[i];, 13번째 줄이 return 줄입니다.
namespace BugHunt;
// 일부러 버그 2개를 넣은 평균 계산 클래스 (디버거로 찾아보기)
public static class ScoreCalculator
{
public static double Average(int[] scores)
{
int sum = 0;
for (int i = 1; i < scores.Length; i++)
{
sum += scores[i];
}
return sum / scores.Length;
}
}
Program.cs입니다. 7번째 줄이 Average를 부르는 줄입니다.
// 디버깅 연습: 평균이 82.8이어야 하는데 이상한 값이 나옵니다
using BugHunt;
int[] scores = { 90, 85, 77, 68, 94 };
Console.WriteLine("점수: " + string.Join(", ", scores));
double average = ScoreCalculator.Average(scores);
Console.WriteLine($"평균: {average}");
Console.WriteLine("기대한 평균: 82.8");
실행 결과입니다.
점수: 90, 85, 77, 68, 94
평균: 64
기대한 평균: 82.8
90, 85, 77, 68, 94의 평균은 82.8이어야 하는데 64가 나왔습니다. 코드를 눈으로만 봐서는 어디가 틀렸는지 찾기 어려울 수 있습니다. 디버거로 찾아봅니다.
중단점 걸고 F5로 실행하기
ScoreCalculator.cs의 11번째 줄sum += scores[i];를 클릭하고 F9를 누릅니다. 줄 왼쪽 여백을 클릭해도 됩니다. 빨간 점이 생기는데, 이것이 중단점(breakpoint)입니다. F9는 중단점 설정/해제(Toggle Breakpoint)의 단축키입니다.- F5를 누릅니다. 디버그 > 디버깅 시작(Debug > Start Debugging)의 단축키입니다.
- 프로그램이 실행되다가 중단점이 있는 줄에서 멈춥니다. 그 줄에 노란 화살표가 표시됩니다.
- 변수 값을 보려면 Alt+4로 Locals 창을 엽니다. 메뉴로는 디버그 > 창(Debug > Windows) 아래에 있습니다. 이 창은 한국어 문서마다 번역이 달라서 영어 이름으로 적었습니다.
- 다시 F5를 누르면 다음 중단점까지 실행합니다. 이 줄은 반복문 안에 있으므로 반복할 때마다 멈춥니다.
- 다 봤으면 Shift+F5(디버깅 중지, Stop Debugging)로 끝냅니다.
노란 화살표가 가리키는 줄은 아직 실행되지 않은 줄입니다. 화살표가 sum += scores[i];에 있을 때 sum에는 이번 점수가 아직 더해지지 않았습니다.
멈출 때마다 값 보기
멈출 때마다 Locals 창에서 i와 sum을 읽고, scores[i]는 코드의 scores[i] 위에 마우스를 올려 데이터 팁(DataTip)으로 확인합니다. Visual Studio 디버거로 같은 위치에 멈추면 아래 값이 보입니다.
| 멈춘 곳 | i | sum | scores[i] |
|---|---|---|---|
| 11번째 줄, 첫 번째 | 1 | 0 | 85 |
| 11번째 줄, 두 번째 | 2 | 85 | 77 |
| 11번째 줄, 세 번째 | 3 | 162 | 68 |
| 11번째 줄, 네 번째 | 4 | 230 | 94 |
| 13번째 줄 return | 반복 끝 | 324 |
첫 번째로 멈췄을 때 i가 벌써 1이고 scores[i]가 85입니다. 0번째 점수 90을 건너뛴 것입니다. 이것이 버그 1입니다. 반복도 다섯 번이 아니라 네 번만 멈춥니다.
13번째 줄에도 중단점을 걸고 멈추면 sum은 324입니다. 324를 5로 나누면 64.8인데 결과는 64입니다. sum과 scores.Length가 둘 다 int라서 정수 나눗셈이 되어 소수점 아래가 버려졌습니다. 지난 글들에서 본 7 / 2가 3이 되는 것과 같은 이유입니다. 이것이 버그 2입니다. return 결과를 double로 받아도, 나눗셈이 이미 정수로 끝났기 때문에 64가 됩니다.
한 줄씩 실행하는 키
중단점에서 멈춘 뒤에는 한 줄씩 실행하면서 흐름을 따라갈 수 있습니다. 아래 세 메뉴는 한국어 문서마다 번역이 달라서 단축키와 영어 이름으로 적었습니다. 메뉴로는 디버그(Debug) 메뉴에 있습니다.
| 키 | 영어 이름 | 하는 일 |
|---|---|---|
| F11 | Step Into | 다음 줄을 실행합니다. 메서드를 부르는 줄이면 그 메서드 안으로 들어갑니다 |
| F10 | Step Over | 다음 줄을 실행합니다. 메서드를 부르는 줄이면 메서드를 끝까지 실행하고 다음 줄에서 멈춥니다 |
| Shift+F11 | Step Out | 지금 메서드의 나머지를 실행하고, 부른 곳으로 돌아가서 멈춥니다 |
Program.cs의 7번째 줄에 중단점을 걸고 멈춘 뒤 키를 눌러 보면 이렇게 움직입니다.
- 7번째 줄에서 멈췄을 때
scores를 보면 90, 85, 77, 68, 94 다섯 개가 들어 있습니다. - F11을 누르면
ScoreCalculator.cs의Average메서드 안으로 들어갑니다. 한 번 더 누르면int sum = 0;줄로 가고, 이때sum은 0입니다. - Shift+F11을 누르면
Average의 나머지를 다 실행하고Program.cs의 7번째 줄로 돌아옵니다. - F10을 누르면 7번째 줄의 대입까지 끝나고 8번째 줄에서 멈춥니다. 이제
average는 64입니다.
이 밖에 Ctrl+F10은 커서 위치까지 실행(Run to Cursor)입니다. 중단점을 걸지 않고 커서가 있는 줄까지 실행하고 멈춥니다.
변수를 보는 여러 방법
| 창 | 여는 방법 | 보여 주는 것 |
|---|---|---|
| Locals | Alt+4 | 지금 메서드의 지역 변수 전부 |
| 자동(Autos) | Ctrl+Alt+V, A | 지금 줄과 바로 앞 줄에서 쓰는 변수 |
| Watch | Ctrl+Alt+W, 1 | 내가 직접 적은 변수나 식 |
| 데이터 팁(DataTip) | 변수 위에 마우스 올리기 | 그 변수 하나의 값 |
| 호출 스택(Call Stack) | Ctrl+Alt+C | 지금 메서드를 누가 불렀는지의 순서 |
Locals와 Watch 창은 한국어 문서마다 번역이 달라서 영어 이름으로 적었습니다. Watch 창에는 변수뿐 아니라 식도 적을 수 있습니다. 13번째 줄에서 멈췄을 때 sum / scores.Length와 (double)sum / scores.Length를 적어 두면 정수 나눗셈과 실수 나눗셈의 결과(64와 64.8)를 나란히 비교할 수 있습니다. 이 두 식의 값은 계산으로 정리한 것이고, Watch 창 화면에서 확인한 것은 아닙니다.
버그 두 개 고치기
찾은 버그 두 개를 고친 ScoreCalculator.cs입니다. BugHuntFixed라는 프로젝트로 따로 만들어서 namespace 이름이 다릅니다. Program.cs는 using BugHuntFixed;인 것만 다르고 앞과 같습니다.
namespace BugHuntFixed;
// BugHunt의 버그 2개를 고친 버전
public static class ScoreCalculator
{
public static double Average(int[] scores)
{
int sum = 0;
for (int i = 0; i < scores.Length; i++) // 고침 1: 0번부터
{
sum += scores[i];
}
return (double)sum / scores.Length; // 고침 2: 실수 나눗셈
}
}
- 고침 1: 반복을 0번째부터 시작합니다.
- 고침 2:
sum을(double)로 바꾼 뒤 나눠서 실수 나눗셈이 되게 합니다.
실행 결과입니다.
점수: 90, 85, 77, 68, 94
평균: 82.8
기대한 평균: 82.8
헷갈리기 쉬운 점
- 노란 화살표가 있는 줄은 아직 실행 전입니다. 그 줄의 결과를 보려면 F10을 한 번 눌러야 합니다.
- 중단점에서 멈추려면 F5로 실행합니다. Ctrl+F5는 디버거 없이 실행하는 것이라 중단점이 있어도 멈추지 않습니다.
- F10과 F11의 차이는 메서드를 부르는 줄에서만 보입니다. 메서드를 부르지 않는 줄에서는 둘 다 다음 줄로 갑니다. 메서드 안으로 잘못 들어갔다면 Shift+F11로 나옵니다.
- F5로 실행하다 예외가 나면 그 줄에서 멈춥니다. 지난 글들에서 본
int.Parse("abc")같은 예외가 나면, 예외 도우미(Exception Helper)가 예외 종류와 메시지를 보여 줍니다. Ctrl+F5로 실행할 때처럼 콘솔에 오류만 찍히고 끝나는 것과 다릅니다. - 메뉴의 한국어 이름보다 단축키를 먼저 익힙니다. F5, F9, F10, F11, Shift+F11, Shift+F5는 버전과 언어가 달라도 같습니다.
6. 조건부 중단점과 Debug·Release

반복이 1000번인 반복문에서 특정 순간만 보고 싶다면 F5를 1000번 누를 수는 없습니다. 이럴 때 조건부 중단점을 씁니다. 장의 뒷부분에서는 Debug와 Release 빌드 구성의 차이를 봅니다.
조건부 중단점
BugHunt의 11번째 줄에서 i가 3일 때만 멈춰 봅니다.
- 11번째 줄의 빨간 점(중단점)을 오른쪽 클릭하고 조건(Conditions)을 누릅니다. 단축키는 Alt+F9, C입니다.
- 중단점 설정(Breakpoint Settings) 창이 열리고 조건(Conditions)이 체크됩니다.
- 첫 번째 목록은 조건식(Conditional Expression)으로 둡니다. 다른 항목으로 적중 횟수(Hit Count)와 필터(Filter)가 있습니다.
- 두 번째 목록은 Is true로 둡니다. 다른 항목으로 When changed가 있습니다. 이 두 항목의 한국어 표시는 환경에 따라 다를 수 있습니다.
- 입력 칸에
i == 3을 적습니다. - 닫기(Close)를 누르거나 Ctrl+Enter를 누릅니다.
조건이 있는 중단점은 빨간 점 안에 + 표시가 생깁니다. 이제 F5로 실행하면 1, 2번째 반복은 멈추지 않고 지나갑니다. 같은 조건(i == 3)으로 처음 멈춘 순간의 값은 i = 3, sum = 162입니다. 앞의 표에서 세 번째로 멈췄을 때와 같은 값입니다.
적중 횟수(Hit Count)를 고르면 "이 줄을 n번째 지날 때 멈춤"처럼 횟수로 조건을 걸 수 있습니다. 걸어 둔 중단점과 조건은 첫 장에서 본 .vs 폴더에 저장됩니다.
Debug와 Release
지금까지 빌드 결과가 bin\Debug 아래에 생긴 것은 빌드 구성(build configuration)이 Debug였기 때문입니다. 빌드 구성은 기본으로 Debug와 Release 두 가지가 있습니다.
- Debug: 디버깅용입니다. 공식 문서 표현으로는 전체 디버그 정보를 넣고 최적화를 하지 않습니다.
- Release: 완성된 프로그램용입니다. 완전히 최적화합니다. 최적화를 하면 코드가 더 빠르게 바뀌는 대신 디버깅이 어려워집니다.
아래 값은 TodoApp.csproj에서 두 구성의 설정을 명령줄(dotnet msbuild -getProperty)로 읽어서 정리한 것입니다.
| 설정 | Debug | Release |
|---|---|---|
| 최적화(Optimize) | false |
true |
| 정의된 기호(DefineConstants) | TRACE;DEBUG |
TRACE;RELEASE |
| 디버그 정보(DebugType) | portable (.pdb 생성) |
portable (.pdb 생성) |
| 출력 폴더 | bin\Debug\net10.0\ |
bin\Release\net10.0\ |
| 쓰는 때 | 개발하고 디버깅할 때 | 다른 사람에게 줄 때 |
구성은 도구 모음의 솔루션 구성(Solution Configurations) 드롭다운에서 Debug나 Release를 골라 바꿉니다. 더 자세한 설정은 Configuration Manager에서 하는데, 이 메뉴도 한국어 문서에 영어로 남아 있습니다. 한국어 화면에서는 "구성 관리자"처럼 보일 수 있지만 한국어 표시는 버전에 따라 다를 수 있습니다.
#if DEBUG: 구성마다 다른 코드
앞 장의 BuildInfo 예제에는 이런 부분이 있었습니다.
#if DEBUG
Console.WriteLine("이 줄은 Debug 빌드에서만 보입니다. (#if DEBUG)");
#else
Console.WriteLine("이 줄은 Release 빌드에서만 보입니다. (#else)");
#endif
#if DEBUG는 DEBUG 기호가 정의돼 있을 때만 그 아래 코드를 컴파일하라는 뜻입니다. 위 표에서 DEBUG 기호는 Debug 구성에만 있습니다. 앞에서 본 Debug 실행 결과에는 #if DEBUG 쪽 줄이 나왔습니다. 같은 프로젝트를 Release로 빌드해서 실행한 결과입니다.
빌드 구성: Release
이 줄은 Release 빌드에서만 보입니다. (#else)
실행 폴더: ...\bin\Release\net10.0\
폴더 안의 파일:
BuildInfo.deps.json
BuildInfo.dll
BuildInfo.exe
BuildInfo.pdb
BuildInfo.runtimeconfig.json
빌드 구성이 Release로 바뀌고, #else 쪽 줄이 나오고, 실행 폴더가 bin\Release\net10.0이 됐습니다.
헷갈리기 쉬운 점
- Release에도 .pdb 파일이 생깁니다. 위 표처럼 두 구성 모두
portable디버그 정보를 만듭니다. 차이는 디버그 정보가 아니라 최적화 여부입니다. - #if DEBUG 안의 코드는 실행만 안 되는 것이 아니라 아예 컴파일되지 않습니다. Release로 빌드한 프로그램에는 Debug 쪽 줄이 들어 있지 않습니다.
- 조건식에는 비교 연산자 ==를 씁니다. 지난 글들에서 본 것처럼 대입하는
=와 비교하는==는 다릅니다. 조건에는i == 3처럼 씁니다. - Release로 바꾼 채 디버깅하면 값이 이상하게 보일 수 있습니다. 최적화 때문에 변수가 사라지거나 줄 순서가 달라 보일 수 있습니다. 디버깅은 Debug 구성에서 합니다.
- 구성을 바꾸면 출력 폴더도 바뀝니다. Release로 빌드했는데
bin\Debug폴더의 실행 파일을 실행하면 옛 결과를 보게 됩니다.
마치며
이번 글에서는 Visual Studio로 콘솔 앱을 만들 때 알아야 할 구조와 디버깅을 정리했습니다.
- 솔루션은 프로젝트를 묶는 작업 공간이고, 프로젝트는 프로그램 하나입니다.
.vs,bin,obj는 남에게 줄 때 빼도 되는 폴더입니다. - 클래스는 파일 하나에 하나씩 두고 namespace로 묶습니다. 최상위 문은 전역 namespace라서
using이 필요합니다. - 최상위 문과
Main메서드는 쓰는 방법만 다르고, 최상위 문은 프로젝트에서 한 파일에만 쓸 수 있습니다. - 빌드 결과는
bin\Debug\net10.0에 생기고, 우리 코드는.dll에,.exe는 그 dll을 실행해 주는 파일입니다. - F9로 중단점을 걸고 F5로 실행한 뒤, F10, F11, Shift+F11로 한 줄씩 따라가며 버그 두 개를 찾았습니다.
- 조건부 중단점으로 원하는 순간에만 멈추고, Debug와 Release의 차이와
#if DEBUG를 확인했습니다.
다음 글은 「C# 콘솔 입출력, ReadLine과 출력 서식」입니다. 지금까지 정해진 데이터로만 동작하던 예제에 사용자 입력을 받는 방법과, 출력을 보기 좋게 맞추는 서식을 다룹니다.
참고 자료
- Visual Studio의 솔루션 및 프로젝트(한국어)
- 솔루션 탐색기 사용(한국어)
- Introduction to projects and solutions
- namespace 키워드
- 최상위 문(Top-level statements)
- 콘솔 앱 템플릿의 최상위 문(한국어)
- 프로젝트와 솔루션 빌드 및 정리(한국어)
- dotnet build 명령
- Visual Studio로 .NET 콘솔 앱 디버그(한국어)
- Visual Studio C# 디버깅 자습서(한국어)
- 디버거로 코드 탐색(Step Into, Step Over, Step Out)
- Autos와 Locals 창
- Watch와 QuickWatch 창
- 중단점 사용(한국어)
- Debug와 Release 구성 설정(한국어)
- .NET 콘솔 앱 게시
'C#' 카테고리의 다른 글
| Windows Forms 시작하기, 폼 만들기와 도구 상자 컨트롤 (0) | 2026.10.06 |
|---|---|
| C# 콘솔 입출력, ReadLine과 출력 서식 (0) | 2026.10.06 |
| C# 고급 문법, 클래스부터 LINQ까지 (0) | 2026.10.06 |
| C# 기본 문법, 변수부터 메서드까지 (0) | 2026.10.06 |
| C# 작업환경, Visual Studio 2026 설치하기 (0) | 2026.10.06 |