
C# 코드가 길어져서 스크롤 압박을 느끼신 적은 없으신가요?
처음엔 편할지 몰라도 나중엔 원하는 클래스를 찾기조차 힘들어집니다.
이번 글에서는 비대해진 코드를 클래스 단위로 .cs 파일을 분리해서,
프로젝트를 깔끔하고 체계적으로 정리하는 방법에 대해 알아보겠습니다.
해당 질문에 대한 이유는 명확합니다.
"코드는 작성하는 시간보다 읽는 시간이 훨씬 깁니다."
가독성: 파일 이름만 보고도 어떤 코드가 들어있는지 바로 알 수 있어요.
유지보수성: 특정 기능을 수정해야 할 때, 해당 클래스 파일만 열면 됩니다.
재사용성: 잘 만들어진 클래스 파일은 다른 프로젝트에 쉽게 가져다 쓸 수 있습니다.
협업: 여러 개발자가 한 프로젝트를 진행할 때, 충돌이 일어날 확률이 크게 줄어듭니다.
처음 프로젝트를 만들면 보통 Program.cs 파일 하나만 있죠.
여기에 Player와 Monster클래스를 작성해 볼게요.
[Program.cs]
using System;
namespace MyGame
{
// 플레이어 클래스
public class Player
{
public string Name { get; set; }
public int Hp { get; set; }
public Player(string name, int hp)
{
Name = name;
Hp = hp;
}
public void Attack(Monster monster)
{
Console.WriteLine($"{Name}이(가) {monster.Name}을(를) 공격합니다!");
}
}
// 몬스터 클래스
public class Monster
{
public string Name { get; set; }
public int Hp { get; set; }
public Monster(string name, int hp)
{
Name = name;
Hp = hp;
}
}
// 프로그램 실행 지점
class Program
{
static void Main()
{
Player player = new Player("용사", 100);
Monster monster = new Monster("슬라임", 20);
player.Attack(monster);
}
}
}
지금은 코드가 짧아서 괜찮아 보이지만, 스킬, 아이템, 상태이상 등
각종 기능이 추가된다면 Program.cs 파일은 금방 복잡해집니다.
실무에서는 여러 개발자가 하나의 프로젝트를 함께 만들어요.
이때 Git과 같은 버전 관리 시스템을 사용하는데,
파일이 합쳐진 경우 충돌(Conflict)이 발생할 확률이 매우 높습니다.
실무에선 '한 파일에 한 클래스' 원칙을 네이밍 컨벤션으로 두는 경우가 많습니다.
이제 Program.cs에 모여 살던 클래스들을 독립시켜 줍시다.
Player클래스를 예시로 차근차근 따라 해 보세요.

1. 비주얼 스튜디오 오른쪽에 있는 솔루션 탐색기 창을 보세요.
2. 여러분의 프로젝트 이름에 마우스를 올리고 우클릭합니다.
3. 메뉴에서 [추가] → [새 항목]을 선택합니다.
주의: 솔루션 이름이 아닌 프로젝트 이름을 선택해야 합니다.

4. 새 항목 추가 창이 나타나면, 클래스(Class)를 선택합니다.
5. 아래쪽 이름 입력란에 'Player.cs'를 적어주고 [추가]를 선택합니다.
일반적인 관례: C#에서는 클래스 이름과 파일 이름이 같게 맞춰두는 경우가 많습니다.

6. Player.cs라는 새로운 파일이 생성되었습니다.
7. Program.cs에 있던 Player클래스 코드를 잘라내서
8. 새로 만든 Player.cs 파일에 붙여넣습니다.
비주얼 스튜디오는 클래스를 빠르게 분리할 수 있는 방법을 제공합니다.
Monster클래스를 예시로 차근차근 따라 해 보세요.

1. 기존 파일에서 분리하려는 클래스 이름에 커서를 놓습니다.

2. (Ctrl+.)키를 누르면 빠른 작업을 할 수 있는 메뉴가 나타납니다.
3. [...(으)로 형식 이동] 옵션을 선택합니다.
4. 해당 클래스의 코드를 새로운 파일을 생성하여 자동 이동시켜 줍니다.

5. Monster.cs라는 새로운 파일이 생성되었습니다.
6. Monster클래스의 코드가 자동으로 이동되었습니다.
자, 그럼 어떻게 변했는지 볼까요?
[Player.cs]
using System; // Console기능을 사용하기 위해서는 필수입니다.
namespace MyGame
{
public class Player
{
public string Name { get; set; }
public int Hp { get; set; }
public Player(string name, int hp)
{
Name = name;
Hp = hp;
}
public void Attack(Monster monster)
{
Console.WriteLine($"{Name}이(가) {monster.Name}을(를) 공격합니다!");
}
}
}
[Monster.cs]
namespace MyGame
{
public class Monster
{
public string Name { get; set; }
public int Hp { get; set; }
public Monster(string name, int hp)
{
Name = name;
Hp = hp;
}
}
}
[Program.cs]
namespace MyGame
{
class Program
{
static void Main()
{
Player player = new Player("용사", 100);
Monster monster = new Monster("슬라임", 20);
player.Attack(monster);
}
}
}
[실행 결과]
용사이(가) 슬라임을(를) 공격합니다!
Program.cs는 이제 프로그램의 실행 흐름에만 집중하고,
Player.cs와 Monster.cs는 각자의 역할에 충실한 코드를 담고있습니다.
여기서 Player.cs에서 Monster타입을 어떻게 알 수 있을까요?
바로 namespace MyGame덕분입니다. 같은 네임스페이스 안에 있는
클래스들은 별도의 using 선언 없이도 서로를 인식할 수 있답니다!
가끔은 하나의 클래스가 너무 커져서, 이 클래스마저도 여러 파일로
나누고 싶을 때가 있습니다. 이럴 때 partial키워드를 사용할 수 있어요.
[Player.Data.cs]
namespace MyGame
{
public partial class Player // partial 키워드 추가!
{
public string Name { get; set; }
public int Hp { get; set; }
public Player(string name, int hp)
{
Name = name;
Hp = hp;
}
}
}
[Player.Skills.cs]
using System;
namespace MyGame
{
public partial class Player // partial 키워드 추가!
{
public void Attack(Monster monster)
{
Console.WriteLine($"{Name}이(가) {monster.Name}을(를) 공격합니다!");
}
public void UseHealPotion()
{
Console.WriteLine("체력을 회복합니다.");
Hp += 30;
}
}
}
컴파일러는 이 두 partial클래스 파일을 하나로 합쳐서 인식합니다.
요즘 C#에서는 최상위문(Top-level statements) 기능과
암시적 전체 사용(Implicit global usings) 기능 덕분에
네임스페이스(Namespace)의 역할에 대해 잠시 잊게 될 수도 있죠.
하지만 잘 지은 건물에 주소 체계가 필수적이듯, 잘 만든 프로그램에는
네임스페이스라는 체계적인 주소 시스템이 반드시 필요하답니다.
우선 최상위문과 암시적 전체 사용의 기능을 살펴보고
왜 네임스페이스가 여전히 중요한지 자세히 알아보겠습니다.
최상위문은 C# 9.0 버전에서 도입된 기능으로, 프로그램의
진입점(Entry Point)을 간결하게 만들어주는 기능입니다.
마치 문자 메시지를 보낼 때 서론, 본론, 결론 양식을 다 갖추지 않고,
핵심 내용만 입력해서 바로 보내는 것과 같아요.
불필요한 코드가 사라져서 코드를 간결하게 작성할 수 있습니다.
최상위 문은 프로젝트 전체에서 단 하나의 파일에만 사용할 수 있고,
새 프로젝트를 만들 때 비주얼 스튜디오로 해당 기능을 켜고 끌 수 있습니다.
보통은 프로그램의 시작점인 Program.cs 파일에서 작성하는 것이 일반적입니다.
[Before]
using System;
namespace MyFirstApp
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
[After]
using System;
Console.WriteLine("Hello, World!");
예전에는 콘솔 앱을 만들때 필요한 기능이 무엇인지
맨 위에 System, System.Linq 등을 일일이 포함해야 했습니다.
C# 10.0 버전부터 암시적 전체 사용을 통해 생략할 수 있습니다.
마치 요리할 때마다 소금, 설탕, 후추를 꺼내놓는 게 아니라,
주방에서 자주 쓰이는 양념통을 항상 준비해 두는 것과 비슷해요.
[Before]
using System;
Console.WriteLine("Hello, World!");
[After]
Console.WriteLine("Hello, World!");

1. 솔루션 탐색기 창에서 프로젝트 이름을 더블클릭하세요.
2. .csproj 파일 내용이 열리는데, 그 안에 아래와 같은 코드가 보일 거예요.
[.csproj]
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <!-- 바로 이 부분! -->
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
3. ImplicitUsings에서 기능을 enable이나 disable로 설정할 수 있어요.

1. 솔루션 탐색기 창에서 [프로젝트] → [속성]을 선택합니다.

2. 여기서 암시적 전체 사용(Implicit global usings) 기능을 확인할 수 있습니다.
전역(Global) using은 C# 10.0 버전부터 도입되었으며
우리가 원하는 네임스페이스를 프로젝트 전체에 적용하는 기능입니다.
프로젝트에 GlobalUsings.cs 같은 파일을 하나 만들고,
아래처럼 global using키워드를 사용해서 적용합니다.
[GlobalUsings.cs]
global using System.Text;
global using MyProject.Services; // 우리가 만든 커스텀 네임스페이스도 OK!
이제 프로젝트 내 어떤 파일에서도 using System.Text;를 따로 선언하지 않아도
StringBuilder같은 기능을 바로 사용할 수 있게 됩니다.
가장 큰 이유는 이름 충돌 방지와 코드의 체계적인 관리입니다.
'김민준'이라는 이름을 가진 사람은 '서울시 강남구의 김민준'과
'부산시 해운대구의 김민준'이 있는 것처럼 세상에는 동명이인이 많습니다.
코드의 세계에서도 이런 구분이 반드시 필요합니다.
Player클래스Player클래스이 두 클래스는 이름은 같지만 역할은 완전히 다릅니다.
이때 네임스페이스를 사용해서 해결할 수 있습니다.
// 게임 캐릭터를 위한 Player
namespace MyGame.Characters
{
public class Player
{
public int Health { get; set; }
public void Attack() { /* ... */ }
}
}
// 비디오 재생기를 위한 Player
namespace MyGame.Video
{
public class Player
{
public void Play() { /* ... */ }
public void Stop() { /* ... */ }
}
}
파일을 저장할 때 '문서', '사진', '음악' 폴더를 만들어 정리하는 것 처럼
네임스페이스를 사용하면 관련 있는 코드들을 논리적인 그룹으로 묶을 수 있어요.
MyGame.Data: 데이터베이스 관련 코드MyGame.Services: 비즈니스 로직 관련 코드MyGame.Controllers: 사용자 입력 처리 관련 코드네임스페이스는 namespace키워드로 선언합니다.
중괄호 {}를 사용해 코드 블록을 감싸는 방식입니다.
namespace MyProject.Services
{
public class MyService
{
// ...
}
}
C# 10.0 버전부터 도입된 기능으로 파일 상단에 세미콜론;으로 선언하면,
해당 파일 전체가 네임스페이스에 속하게 됩니다. 훨씬 깔끔하죠!
namespace MyProject.Services; // 파일 전체에 적용
public class MyService
{
// ...
}
MyGame.Characters.Player처럼 전체 주소를 쓰는 건 번거롭죠?
이럴 때 사용하는 것이 바로 using지시문입니다.
코드 맨 위에 "앞으로 이 서랍장을 자주 쓸 거야!"라고 알려주는 것과 같아요.
[예시]
// using 지시문: "앞으로 MyGame.Characters 서랍장을 자주 사용함!"
using MyGame.Characters;
namespace MyGame.Characters
{
public class Player
{
public int Health { get; set; }
public void Attack() { /* ... */ }
}
}
namespace MyGame.Video
{
public class Player
{
public void Play() { /* ... */ }
public void Stop() { /* ... */ }
}
}
class Program
{
static void Main()
{
// 이제 'MyGame.Characters.'을 생략하고 바로 Player를 사용할 수 있습니다.
Player gamePlayer = new Player();
gamePlayer.Attack();
// MyGame.Video.Player는 using으로 선언하지 않았으므로, 전체 주소를 써야 합니다.
MyGame.Video.Player musicPlayer = new MyGame.Video.Player();
musicPlayer.Play();
}
}
여기서 만약 using MyGame.Characters;와 using MyGame.Video;를 둘 다 선언하면
Player가 어느 네임스페이스의 타입을 가리키는지 애매해져서 컴파일 오류가 발생합니다.
이럴 때는 using대신 전체 이름을 쓰거나,
using VideoPlayer = MyGame.Video.Player;처럼 별칭을 사용하는 방법도 있습니다.