Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 

.NET SDK 10 - 단일 소스 코드 파일을 빌드/실행하는 기능을 "dotnet" 명령어에 추가

실행까지는 아니었지만, 과거에도 단일 소스 코드 파일을 컴파일하는 것이 가능했습니다. 일례로, .NET Framework 시절에는 고정된 위치에 프레임워크가 설치됐고, 그 구성 요소에는 C# 컴파일러(csc.exe)도 함께 있었습니다. (msbuild.exe가 프로젝트 단위의 빌드를 담당했다면,) csc.exe는 소스 코드 파일을 컴파일하는 역할을 했는데요, 당연히 소스 코드 파일 1개도 빌드가 가능했습니다.

이후 .NET Core가 등장하면서 초기에 잠깐 csc.exe로 제공되다가 .NET 2.2.2 SDK부터는 csc.exe가 누락되고 아예 csc.dll로 제공되기 시작했습니다. 따라서 단일 소스 코드 파일을 컴파일하려면 dotnet.exe + csc.dll의 조합으로 컴파일해야 했는데요, 관련해서는 다음의 글에 자세하게 설명했으니 참고하시고.

.NET Core/5+ 환경에서 (프로젝트가 아닌) C# 코드 파일을 입력으로 컴파일하는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/13105

어찌 보면, 사실상 .NET 9 환경까지는 단일 소스 코드 파일의 빌드를 공식적으로 지원한다고 보기에는 다소 무리가 있었는데요, 이런 애매함이 .NET 10부터 사라지게 되었습니다. 게다가 방법도 간단해졌는데요, 기존의 dotnet 실행 파일에 그 기능을 통합시켰기 때문입니다.

간단하게 실습을 해볼까요? ^^ 어떤 식으로든 단일 C# 코드 파일을 하나 만든 후, 다음과 같이 "dotnet build"로 전달만 하면,

c:\temp> type test.cs
Console.WriteLine("Hello world");

c:\temp> dotnet build test.cs

컴파일이 됩니다. 문제가 하나 있다면, 저렇게 빌드한 결과물의 위치가 매우 복잡하다는 점입니다.

%USERPROFILE%\AppData\Local\Temp\dotnet\runfile\test-6dee3fbcf5d3d00ed309fd6226803f671db092c17093e603ca20d6880b18d12c\bin\debug\test.dll

// 위의 경로는 빌드 시 -v 옵션을 주면 쉽게 확인할 수 있습니다.
c:\temp> dotnet build -v d test.cs
Restore complete (0.4s)
    All projects are up-to-date for restore.
    info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
  test succeeded (0.2s) → C:\Users\testusr\AppData\Local\Temp\dotnet\runfile\test-6dee3fbcf5d3d00ed309fd6226803f671db092c17093e603ca20d6880b18d12c\bin\debug\test.dll

Build succeeded in 1.4s

그래서 보통은 output 디렉터리를 지정해서 빌드하게 될 텐데요,

c:\temp> dotnet build test.cs -o .

c:\temp> dotnet test.dll
Hello world

여기서 더 재미있는 점은, 단일 소스 코드 파일에 대한 빌드뿐만 아니라 실행까지도 dotnet 명령어가 지원한다는 점입니다. 그래서 위의 명령어처럼 2개로 나뉠 필요 없이 곧바로 "dotnet run"으로 실행할 수 있습니다.

c:\temp> dotnet run test.cs
Hello world

이게 뭘 의미하냐면, 컴파일 언어에 속한 C#을 마치 스크립트 언어처럼 다룰 수 있게 되었다는 점입니다. ^^ 심지어 *NIX 환경에서는 이에 대한 특징이 더욱더 잘 드러나는데요, 왜냐하면 소스 코드 파일에 shebang 지시자를 담을 수 있기 때문입니다.

$ cat test.cs
#!/usr/bin/dotnet run

Console.WriteLine("Hello world");

이 상태에서 저 텍스트 파일에 실행 권한만 있다면, 단독으로 실행하는 것도 가능합니다.

$ chmode +x test.cs
$ ./test.cs
Hello world

오호~~~ 나름 괜찮은 기능이 추가된 것 같죠? 이로써 그동안 C#을 스크립트 언어처럼 사용하기 위한 부가 프로젝트들이 더 이상 필요 없게 되었습니다. ^^




그나저나, 기본적인 내용은 저렇게 컴파일과 실행이 가능하다는 건데요, 이게 또 들어가 보면 곁가지들이 좀 나옵니다. 왜냐하면, 그동안 프로젝트 파일(csproj)을 통해 관리를 했던 부분들이 날아갔기 때문에 그 빈자리가 아쉬울 수밖에 없고, 그래서 이에 대한 보완으로 5개 정도의 지시자가 추가됐습니다. (향후, 더 나올 수도 있습니다.)

  • #! (shebang)
  • #:sdk (SDK 설정)
  • #:property (속성 설정)
  • #:package (nuget 패키지 참조)
  • #:project (외부 프로젝트 참조)

첫 번째 shebang 지시자는 이미 위에서 설명했으니 넘어가고, 두 번째 지시자부터 하나씩 알아보겠습니다.


#:sdk 지시자

예를 들어, Console 응용 프로그램과 ASP.NET Core 웹 응용 프로그램의 경우 SDK 유형부터가 다릅니다.

<Project Sdk="Microsoft.NET.Sdk">
    <!-- ...[생략]... -->
</Project>

<Project Sdk="Microsoft.NET.Sdk.Web">
    <!-- ...[생략]... -->
</Project>

따라서, 단일 파일의 경우 저 SDK 유형을 지정할 방법이 필요했고, 그 용도로 "#:sdk" 지시자가 도입된 것입니다. 이를 응용하면 ASP.NET Core 웹 응용 프로그램도 단일 소스 코드 파일로 작성할 수 있습니다.

$ cat test.cs
#!/usr/bin/dotnet run
#:sdk Microsoft.NET.Sdk.Web

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

$ dotnet run test.cs
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /home/testusr


#:property 지시자

sdk 유형 외에 프로젝트 파일에서 거의 반드시 등장하는 것이 바로 속성(property) 설정인데요,

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

이것을 소스 코드 파일에 지정이 가능하도록 다음의 유형으로 "#:property" 지시자를 추가할 수 있습니다.

#:property [속성]=[값]

이에 대한 예제로, WinRT API를 사용하는 단일 소스 코드 파일을 이렇게 작성할 수 있습니다.

#:sdk Microsoft.NET.Sdk.WindowsDesktop
#:property TargetFramework=net10.0-windows10.0.17763.0

using Windows.Devices.Enumeration;

// 이 코드는 WinRT를 사용하므로 *NIX 환경에선 동작하지 않습니다.

using Windows.Devices.Enumeration;

// https://www.sysnet.pe.kr/2/0/11284
await ListDevices();

static async Task ListDevices()
{
    var devices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);

    foreach (var item in devices)
    {
        Console.WriteLine($"{item.Id}: {item.Name}");
    }
}


#:package 지시자

그다음 프로젝트 파일의 역할 중 가장 중요한 것으로는 패키지 참조가 있는데요, 이에 대응해서는 "#:package" 지시자를 제공합니다.

#:package [패키지_이름]@[버전]

nuget에서 제공하는 패키지를 참조할 수 있게 해주는데, 아래의 예제는 Newtonsoft.Json 13.0.3 버전을 참조하는 단일 소스 코드 파일을 보여줍니다.

$ cat test.cs
#:package Newtonsoft.Json@13.0.3

using Newtonsoft.Json;

Product product = new Product();

product.Name = "Apple";
product.Price = 3.99M;

string output = JsonConvert.SerializeObject(product);
Console.WriteLine(output);

class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

$ dotnet run test.cs
{"Name":"Apple","Price":3.99}


#:project 지시자

마지막으로, nuget 패키지뿐만 아니라 로컬의 프로젝트 참조도 가능하게 해주는 "#:project" 지시자가 있습니다. 예를 들어, 파일/디렉터리 구조가 다음과 같을 때,

c:\temp> tree /F
Folder PATH listing
Volume serial number is 2E94-0194
C:.
│   test.cs
│
└───ClassLibrary1
        Class1.cs
        ClassLibrary1.csproj

test.cs 파일에서 ClassLibrary1.csproj의 경로를 project 지시자로 참조해 실행할 수 있습니다.

c:\temp> type test.cs
#:project .\ClassLibrary1\ClassLibrary1.csproj

ClassLibrary1.Class1.HelloWorld();

c:\temp> type .\ClassLibrary1\Class1.cs
namespace ClassLibrary1;

public class Class1
{
    public static void HelloWorld()
    {
        System.Console.WriteLine("Hello world");
    }
}

c:\temp> dotnet run test.cs
Hello world




정리하면, "dotnet build", "dotnet run" 명령어의 인자로 단일 C# 소스 코드 파일을 지정할 수 있게 되었고, 이로 인해 5개의 지시자("#:", "#:sdk", "#:property", "#:package", "#:project")를 추가해 프로젝트 파일의 기능을 일부 대체했습니다.

그런데 혹시나, 프로젝트 파일의 특정 기능이 필요한 상황이 있을까요? 그런 경우의 대안으로 Directory.Build.props 파일 역시 유효하므로 현재는 아쉬운 대로 이것을 활용하시면 됩니다.

(참고로 글을 쓰면서 느낀 건데, 이제 블로그에서의 웬만한 예제는 단일 파일 기준으로 작성해도 될 것 같습니다. ^^)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 8/19/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  [23]  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13448정성태11/20/202312338닷넷: 2163. .NET 8 - Dynamic PGO를 결합한 성능 향상파일 다운로드1
13447정성태11/16/202313468닷넷: 2162. ASP.NET Core 웹 사이트의 SSL 설정을 코드로 하는 방법
13446정성태11/16/202314424닷넷: 2161. .NET Conf 2023 - Day 1 Blazor 개요 정리
13445정성태11/15/202315585Linux: 62. 리눅스/WSL에서 CA 인증서를 저장하는 방법
13444정성태11/15/202314611닷넷: 2160. C# 12 - Experimental 특성 지원
13443정성태11/14/202313109개발 환경 구성: 687. OpenSSL로 생성한 사용자 인증서를 ASP.NET Core 웹 사이트에 적용하는 방법
13442정성태11/13/202312333개발 환경 구성: 686. 비주얼 스튜디오로 실행한 ASP.NET Core 사이트를 WSL 2 인스턴스에서 https로 접속하는 방법
13441정성태11/12/202313633닷넷: 2159. C# - ASP.NET Core 프로젝트에서 서버 Socket을 직접 생성하는 방법파일 다운로드1
13440정성태11/11/202311811Windows: 253. 소켓 Listen 시 방화벽의 Public/Private 제어 기능이 비활성화된 경우
13439정성태11/10/202313863닷넷: 2158. C# - 소켓 포트를 미리 시스템에 등록/예약해 사용하는 방법(Port Exclusion Ranges)파일 다운로드1
13438정성태11/9/202314923닷넷: 2157. C# - WinRT 기능을 이용해 윈도우에서 실행 중인 Media App 제어
13437정성태11/8/202315008닷넷: 2156. .NET 7 이상의 콘솔 프로그램을 (dockerfile 없이) 로컬 docker에 배포하는 방법
13436정성태11/7/202315120닷넷: 2155. C# - .NET 8 런타임부터 (Reflection 없이) 특성을 이용해 public이 아닌 멤버 호출 가능
13435정성태11/6/202312785닷넷: 2154. C# - 네이티브 자원을 포함한 관리 개체(예: 스레드)의 GC 정리
13434정성태11/1/202314218스크립트: 62. 파이썬 - class의 정적 함수를 동적으로 교체
13433정성태11/1/202312088스크립트: 61. 파이썬 - 함수 오버로딩 미지원
13432정성태10/31/202313593오류 유형: 878. 탐색기의 WSL 디렉터리 접근 시 "Attempt to access invalid address." 오류 발생
13431정성태10/31/202313693스크립트: 60. 파이썬 - 비동기 FastAPI 앱을 gunicorn으로 호스팅
13430정성태10/30/202314628닷넷: 2153. C# - 사용자가 빌드한 ICU dll 파일을 사용하는 방법
13429정성태10/27/202314491닷넷: 2152. Win32 Interop - C/C++ DLL로부터 이중 포인터 버퍼를 C#으로 받는 예제파일 다운로드1
13428정성태10/25/202315112닷넷: 2151. C# 12 - ref readonly 매개변수
13427정성태10/18/202312632닷넷: 2150. C# 12 - 정적 문맥에서 인스턴스 멤버에 대한 nameof 접근 허용(Allow nameof to always access instance members from static context)
13426정성태10/13/202313978스크립트: 59. 파이썬 - 비동기 호출 함수(run_until_complete, run_in_executor, create_task, run_in_threadpool)
13425정성태10/11/202314847닷넷: 2149. C# - PLinq의 Partitioner<T>를 이용한 사용자 정의 분할파일 다운로드1
13423정성태10/6/202314405스크립트: 58. 파이썬 - async/await 기본 사용법
13422정성태10/5/202312753닷넷: 2148. C# - async 유무에 따른 awaitable 메서드의 병렬 및 예외 처리 [1]
... 16  17  18  19  20  21  22  [23]  24  25  26  27  28  29  30  ...