.NET 10 SDK 10.0.300이 한국 시간으로 13일 새벽에 배포되었습니다. 여느 때라면 단순 품질 개선 업데이트 정도의 의미로 통용되었겠으나 이번 300번대 릴리스에서는 .NET 11 정도에 배포될 예정이었던 File-based App에서 제일 아쉬웠던 파일 단위 Include 지원이 Back Porting 되는 것이어서 매우 중요한 의미가 있습니다.
.NET 10 SDK 10.0.300 번대 릴리스를 설치한 후, dotnet –-list-sdks 명령어를 실행했을 때 10.0.300 버전 (혹은 그 이후 버전)이 목록에 나타나면 설치가 잘 된 것입니다. 그런 후에 아래 파일 2개를 넣어 dotnet run app.cs 명령어를 실행했을 때 빌드 오류가 발생하지 않는지 확인해보시면 되겠습니다.
student.cs
public sealed record class Student(string Name, int Age);
app.cs
(Note) Windows에서도 / 디렉터리 경로 구분자를 사용하는 것이 잘 작동합니다.
#!/usr/bin/env dotnet
#:include ./student.cs
var test = new Student("a", 1);
Console.WriteLine($"{test.Name} {test.Age}");
2-Depth 이상 include를 하는 기능은 아직 opt-in으로 플래그를 걸어야 쓸 수 있습니다. 예를 들어 app.cs가 sample.cs를 include하고, sample.cs가 또 다른 test.cs 파일을 참조하는 상황이라면 app.cs나 sample.cs에서 아래 지시자 선언이 필요합니다.
전에는 파일 서두에 넣어야 할 지시자가 많아서 복잡하고 추천하기 힘들었지만, C/C++ 느낌의 헤더 파일 같은 C# 파일을 사용한다면 다음과 같은 구성이 가능할 것입니다.
native.cs
#:property ExperimentalFileBasedProgramEnableTransitiveDirectives=True
#:property AllowUnsafeBlocks=true
#:property PublishAot=true
#:property ImplicitUsings=disable
global using System;
global using System.Runtime.InteropServices;
app.cs
#!/usr/bin/env dotnet
#:include ./native.cs
unsafe {
const int N = 10;
long* fib = (long*)NativeMemory.Alloc((nuint)(N * sizeof(long)));
try
{
long* p = fib;
long* end = fib + N;
*p++ = 0;
*p++ = 1;
for (; p < end; p++)
*p = *(p - 1) + *(p - 2);
for (p = fib; p < end; p++)
Console.WriteLine(*p);
}
finally
{
NativeMemory.Free(fib);
}
}
그리고 당연한 이야기입니다만 dotnet publish app.cs 로 실행 파일을 만들면 Self-Contained로 .NET 런타임 없이 독립 실행되는 바이너리가 1MB 수준으로 떨어집니다. 이는 C/C++ 코드를 static linking한 것과도 견주어볼 수 있는 동일 리그이고, 딱 하나의 비효율을 짚자면 가비지 컬렉터 정도가 남는 부분이 될 것 같습니다.