메모장으로 C# 코딩하기 - 파일 기반 앱 더 살펴보기

남정현·2025년 11월 3일
0
post-thumbnail

계속 글을 읽기 전에: 파일 기반 앱을 즐겁게 코딩하려면, 먼저 지난 글에서 소개한대로 C# 확장을 정확하게 설치해주셔야 합니다!

파일 기반 앱 문법이란?

파일 기반 앱이 보통의 C# 코드와 다른 점은, 파일 기반 앱 전용 문법이 존재한다는 점이고, 이 문법들은 기존에 csproj 파일 안에서만 지정할 수 있었던 기능들을 코드 레벨로 가져온 것이 차이점입니다.

이번 아티클에서는 파일 기반 앱 문법을 소개하며, 기존의 C# 스크립트와 무엇이 비슷하고 다른지 설명하도록 하겠습니다.

Shebang 라인

유닉스 계통의 모든 OS들이 기본으로 제공하는 기능 중에 Shebang 라인이라는 기능이 있습니다. Shebang 라인으로 시작하는 파일은, 현재 사용자에게 실행 가능 권한을 부여해둘 경우, 컴파일러로 컴파일한 기계어 파일이 아니어도 텍스트 파일 형태의 스크립트를 실행 파일처럼 실행할 수 있게 도와줍니다.

예를 들어, 다음과 같이 텍스트 파일의 제일 처음에 쓸 수 있습니다.

#!/bin/sh

혹은, 좀 더 범용성을 살리고 환경마다 발생할 수 있는 편차를 완화하기 위하여 다음과 같이 쓸 수도 있습니다.

#!/usr/bin/env sh

그러면 .NET의 경우는 어떻게 쓸까요? 여러 방법이 있겠지만, 가장 무난한 것은 env 명령어를 활용하는 것입니다. 아래 Shebang 라인을 사용하면, 현재 셸 프로필의 PATH 설정을 존중하기에 가장 호환성이 높다고 할 수 있습니다.

#!/usr/bin/env dotnet

.NET 10 SDK부터는 위와 같이 지정할 경우 dotnet 명령어에서 첫 인자를 C# 파일 경로로 지정하거나, -를 파일 이름으로 지정하고 STDIN으로 텍스트 파일을 불러오는 것도 가능합니다.

이외에도 아래와 같은 변형을 사용할 수도 있습니다. 패키지 매니저 등을 이용하여 시스템 레벨에 설치한다면 대개 아래와 같이 설치되는 것을 기대할 수 있기 때문에 괜찮은 선택입니다.

#!/usr/local/share/dotnet/dotnet run

혹은 JBang처럼 변형된 Shebang 라인을 사용하는 것도 가능합니다.

///usr/bin/env dotnet "$0" "$@" ; exit $?

원하는 스타일로 Shebang 라인을 설정한 다음, 곧바로 아래와 같이 Top-Level Program 코드를 작성하기 시작하면 유효한 C# 프로그램이 됩니다. 이 때, #!으로 시작하든, //로 시작하든 C# 컴파일러 입장에서는 .NET 10을 기점으로 모두 주석으로 처리하므로 유효합니다.

#!/usr/bin/env dotnet
Console.WriteLine("Hello, World!");

표준 입력 (STDIN) 활용하기

Shebang 라인에서 파일 경로를 dotnet 명령으로 바로 지정할 수 있는 것을 응용해서, 파일 이름에 -를 지정하면 일반적인 유닉스 명령어처럼 표준 입력 (STDIN)으로 내용을 입력받게 할 수 있습니다.

예를 들어, 다음과 같이 실행할 수 있습니다.

echo 'Console.WriteLine("Hello, World!");' | dotnet run -

더 나아가서, 아래처럼 명령어를 실행할 수도 있습니다.

curl -Ls https://tinyurl.com/csharp-fba | dotnet run -

파일 기반 앱의 주요 설정 이해하기

파일 기반 앱에서 변경할 수 있는 설정은, csproj 파일에서만 지정할 수 있던 SDK, 패키지 참조, 프로젝트 컴파일/빌드 설정, 프로젝트 참조를 csproj 파일 없이 C# 코드만으로 지정할 수 있다는 것이 차이점입니다.

물론 csproj 파일의 모든 부분을 대체할 수 있는 것은 아니기에 명확한 한계는 있으나, 시작 단계에서 빠르게 프로토타이핑하기에는 더할 나위 없이 충분하고 강력한 기능을 제공합니다.

  • #:sdk: 사용할 .NET SDK 타입을 지정합니다. 지정하지 않으면, 자동으로 Microsoft.NET.Sdk가 사용됩니다.
  • #:package: 사용할 NuGet 패키지 정보를 지정합니다. 기본적으로 nuget.org에 게시된 패키지를 사용하는 것을 전제로 합니다.
  • #:project: C# 파일이 위치한 곳을 기준으로 하위 폴더 또는 절대 경로 상의 다른 csproj 파일을 참조하여 기존 프로젝트를 가져와서 사용할 수 있습니다.
  • #:property: csproj 수준에서 변경하려는 설정을 이곳에서 지정할 수 있습니다. (MSBuild 설정이라고 흔히 부르는 것을 여기서 바꿀 수 있고, 지정 가능한 옵션도 그래서 무궁무진합니다.)

위의 네 가지 지시자를 사용하여 csproj 파일 없이 C# 애플리케이션의 속성을 변경하거나 정의하여, 단순 콘솔 애플리케이션만이 아니라 윈폼, WPF, Aspire, ASP.NET 등 매우 다양한 애플리케이션을 파일 기반 앱으로 빠르게 프로토타이핑해볼 수 있습니다.

C# 스크립트와 비슷하게 보이지만 다른 점

파일 기반 앱을 보면서, 어디서 많이 봤는데? 하면서 기시감을 느끼시는 분들도 많이 계실겁니다. 맞습니다. 비슷한 시스템을 가지고 있는 것이 사실이죠!

그러나 결정적인 차이점이 몇 가지 있습니다.

  • C# 스크립트는 파일 단위로 실행할 수 있는 개념 외에 스크립트 언어의 핵심이라할 수 있는 REPL (Read, Evalulate, Print, Loop) 개념을 중요하게 생각합니다. 그래서 내부적으로는 코드를 컴파일하지만, 만들어진 어셈블리들을 계속 "한 세션" 안에서 관리합니다.
  • C# 스크립트는 내부적으로는 MSBuild 프로젝트를 동적으로 만들어 컴파일하는 구조를 취하는 것은 맞지만, 최종 산출물이 애플리케이션이 아닌 스크립트의 형태를 유지하는 것이어서 프로젝트 설정을 자유롭게 변경하기 어렵고, 만들 수 있는 애플리케이션 타입도 한정됩니다.
  • 따라서 빌드 설정, 종속성 관리, 출력 형식 지정 등 C# 프로젝트에서 제공하던 모든 기능을 그대로 사용할 수 있습니다.

결국 두 방식은 겉보기 문법은 비슷하지만, 실행 모델과 결과물은 전혀 다릅니다. 파일 기반 앱은 '스크립트처럼 보이지만 애플리케이션으로 작동하는' 새로운 접근이라 할 수 있습니다.

파일 기반 앱이 일반적인 C# 프로그램과 다른 점 두 가지

파일 기반 앱은 일반적인 C# 프로그램과 다른 점이 두 가지 정도가 있어서 잠시 짚고 넘어가겠습니다.

Native AOT

파일 기반 앱은 기본적으로 Native AOT(Ahead-of-Time) 설정이 활성화된 상태로 빌드됩니다.

일반적으로 .NET으로 만든 애플리케이션을 실행하려면, 해당 시스템에 .NET 런타임이 미리 설치되어 있거나 프로그램과 함께 포함되어야 합니다. 한때 “닷넷 프레임워크를 깔아야 한다”, “자바를 설치해야 한다”는 말을 들었던 이유가 바로 이것입니다.

.NET(그리고 Java)에서는 코드를 CPU가 직접 실행 가능한 기계어로 바로 변환하지 않고, 중간 언어(IL, Intermediate Language) 형태로 컴파일합니다. 이 코드를 실행 시점에 다시 기계어로 번역하는 역할을 하는 것이 JIT(Just-In-Time) 컴파일러입니다. 이 구조 덕분에 동일한 코드가 다양한 플랫폼(Windows, Linux, macOS, Android 등)에서 실행될 수 있습니다.

하지만 JIT 기반 구조에는 두 가지 단점이 있습니다.

  • 실행 초기 지연(런타임에서 코드 번역)
  • 비교적 큰 런타임 의존성 및 배포 크기

오늘날에는 Electron처럼 런타임을 포함해 배포하는 모델이 일반적이긴 하지만, 여전히 실행 속도와 배포 용량 측면에서 부담이 남습니다.

이 문제를 해결하기 위해 등장한 것이 AOT(Ahead-of-Time) 컴파일입니다. AOT는 코드를 미리 특정 CPU/OS용 기계어로 변환하여, 런타임 없이 바로 실행할 수 있도록 합니다. 이 방식은 GC나 핵심적인 런타임 기능만 남기고, C/C++이나 Rust에 가까운 실행 성능을 제공합니다. 이로 인해 리플렉션, 동적 로딩 등의 고급 기능은 제약을 받습니다.

파일 기반 앱은 이러한 특성을 활용하여, 기본적으로 JIT 대신 AOT를 사용하도록 설계되었습니다. 이를 통해 유닉스나 리눅스 환경에서 셸 도구나 경량 서버를 만들 때 더 빠르고 독립적인 실행 환경을 구현할 수 있습니다.

물론 이 설정은 언제든 변경할 수 있습니다. 다음과 같이 지시자를 추가하면 됩니다.

#:property PublishAot=false

이렇게 하면 기존의 JIT 기반 .NET 애플리케이션과 동일한 방식으로 동작합니다.

스크립트 파일 경로

일반적인 스크립트 언어랑 달리, C#은 이런 환경에서 실행이 지원된다고 하더라도 여전히 "컴파일러 언어"입니다. 하지만 사람이 보기에는 마치 스크립트가 실행되는 것처럼 패키징되어있다보니 약간의 "개념적 혼란"이 발생하기 쉬운데요!

만들어질 EXE 파일 기준이 아니라, C# 파일의 경로를 얻어올 수 있는 방법을 파일 기반 앱 환경에서만 특별히 따로 제공합니다.

아래처럼 사용하면, 실행에 쓴 C# 파일의 절대 경로를 얻을 수 있습니다.

AppContext.GetData("EntryPointFilePath")

파일 대신 디렉터리 경로를 얻고 싶다면, 아래 처럼 사용할 수 있습니다. 단, 이 경로는 Environment.CurrentDirectory와는 다른 경로이며, 이는 유닉스 셸 스크립트와 마찬가지인 부분입니다.

AppContext.GetData("EntryPointFileDirectoryPath")

파일 기반 앱과 기존 프로젝트와의 관계

마지막으로, 파일 기반 앱과 기존 프로젝트와의 관계에 대해서도 짚고 넘어갈 필요가 있겠습니다. 크게 세 가지 토픽이 있겠네요!

파일 기반 앱에서 기존 프로젝트 참조하기

앞에서, #:project 지시자에 대해서 이야기해보았는데요, 이 지시자를 사용할 때 뒤에 지정할 csproj 파일의 경로는 절대 경로도 가능하고, 현재 저장된 파일 기준의 상대 경로도 가능합니다.

이런 방식을 사용하여 기존 프로젝트를 참조하면, 단위 테스트 프로젝트를 만들어서 프로젝트 기능을 평가하는 것과는 또 다른 방식으로 테스트 프로젝트를 만들거나 CI/CD 기능 구현을 생각해볼 수 있습니다.

그리고 나중에 살펴볼 .NET Aspire의 AppHost 프로젝트처럼 프로젝트 단위 참조를 걸어야 하는 상황에서도 매우 잘 어울리는 기능이 됩니다. :-D

파일 기반 앱이 지원하지 않는 MSBuild 설정 관리하기

파일 기반 앱의 최대 장점은 프로젝트 파일을 만들지 않고, 단 하나의 cs 파일만을 써서 애플리케이션을 개발할 수 있다는 점입니다. 그렇지만, 필요성은 다소 낮지만, 그럼에도 csproj 파일에 의존하지 않는 개발 환경 (다시 말해 Visual Studio나 C# Dev Kit에 의존하지 않는 개발 환경)을 지키고 싶은 분들을 위한 여지도 여전히 남겨두고 있습니다.

파일 기반 앱의 지시자 만으로 커버가 되지 않거나 조건부 빌드 설정 등 여전히 SDK 스타일 XML 구성이 필요하다면, Directory.Build.props 파일을 만들고 같은 위치에 놓아두면 C# LSP 서버가 이를 자동으로 인식합니다. 만약 팀 내에서 사용하는 전용 NuGet 피드가 있다면 Nuget.config 파일도 같은 위치에 두면 됩니다.

예를 들어, IKVM이라는 .NET 기반의 Java VM 구현체를 사용하면서, JSoup 메이븐 패키지에 대한 참조를 걸기 위해 IKVM 전용 Item을 사용해야 하는데, 이 때 Directory.Build.props 파일을 다음과 같이 작성해서 넣어둘 수 있습니다.

#:package IKVM.Maven.Sdk@1.9.4
...
<Project>
    <ItemGroup>
        <MavenReference Include="org.jsoup:jsoup" Version="1.17.2" />
    </ItemGroup>
</Project>

이외에도 이런 식으로 작성하고 배치할 수 있는 파일들은 다음과 같습니다.

global.json, NuGet.config, Directory.Build.props, Directory.Build.targets, Directory.Packages.props, Directory.Build.rsp, MSBuild.rsp

파일 기반 앱을 일반 C# 프로젝트로 승격하기

그리고 어느 정도 개발이 완료되고 규모도 커졌다고 느껴진다면, .csproj 파일로 업그레이드/승격하는 것을 간소화해주는 도구도 사용 가능합니다.

dotnet project convert Program.cs 명령어를 사용하면 손쉽게 .csproj 기반 프로젝트로 업그레이드/승격이 가능합니다.

마무리

이번 아티클에서는 파일 기반 앱의 문법과 기존에 커뮤니티에서 개발되었던 C# 스크립트와 무엇이 다른지, 그리고 파일 기반 앱의 동작 특성에 대해서 살펴보았습니다.


참고 자료

0개의 댓글