.NET 10 Preview 시리즈 상세 요약
개요
Andrew Lock의 .NET Escapades 블로그에서 발행된 “.NET 10 Preview 탐색” 시리즈는 .NET 10의 프리뷰 기능들을 심도 있게 다룬 7편의 글로 구성되어 있습니다. 각 글은 .NET 10에서 도입된 새로운 기능들을 실제 예제와 구현 세부사항을 통해 상세히 설명합니다.
Part 1: dotnet run app.cs 기능 탐색
주요 내용
.NET 10의 새로운 단일 파일 실행 기능
.NET 10에서는 프로젝트 파일(.csproj) 없이도 단일 C# 파일만으로 애플리케이션을 실행할 수 있는 혁신적인 기능이 도입되었습니다.
핵심 기능들
1. 기본 사용법
# 단순한 Hello World
Console.WriteLine("Hello, world");
# app.cs로 저장 후
dotnet run app.cs
2. 지원되는 지시문들
#:sdk 지시문 - SDK 참조
#:sdk Microsoft.NET.Sdk.Web
#:sdk Aspire.AppHost.Sdk 9.3.0
#:package 지시문 - NuGet 패키지 참조
#:package Newtonsoft.Json@13.0.3
#:package Aspire.Hosting.AppHost@*
#:package SomePackage@9.*
#:property 지시문 - MSBuild 속성 설정
#:property UserSecretsId 2eec9746-c21a-4933-90af-c22431f35459
#:project 지시문 - 프로젝트 참조 (Preview 6에서 예정)
#:project ../src/MyProject
#:project ../src/MyProject/MyProject.csproj
3. Shebang 지원
#!/usr/bin/dotnet run
Console.WriteLine("Hello from script!");
Unix 시스템에서 직접 실행 가능합니다.
대상 사용자
- .NET 입문자: 프로젝트 파일의 복잡성 없이 학습 가능
- 유틸리티 스크립트: Bash나 PowerShell 대신 C# 사용
- 샘플 애플리케이션: 각 .cs 파일이 독립적인 샘플
- 기존 스크립팅 도구 사용자: Cake, dotnet-script, CS-Script 등에서 전환
예정된 기능들
dotnet publish 지원 (Preview 6)
dotnet publish app.cs
# 기본적으로 NativeAOT 앱으로 빌드됨
dotnet app.cs 직접 실행
# 기존: dotnet run app.cs
# 새로움: dotnet app.cs
stdin에서 C# 코드 실행
echo 'Console.WriteLine("Hello, World!");' | dotnet run -
변환 기능
# 단일 파일을 정규 프로젝트로 변환
dotnet project convert app.cs
고급 활용
- Escape Hatches: global.json, NuGet.config, Directory.Build.props 등 지원
- 다중 파일 지원: .NET 11에서 계획됨
- Visual Studio 지원: Visual Studio Code만 지원 (Visual Studio는 미지원)
Part 2: dotnet run app.cs 구현 내부
구현 아키텍처
이 기능은 “가상” .csproj 파일을 생성하여 기존 .NET 빌드 시스템을 활용하는 방식으로 구현되었습니다.
처리 단계
1. 단일 파일 프로그램 식별
public static bool IsValidEntryPointPath(string entryPointFilePath)
{
if (!File.Exists(entryPointFilePath))
return false;
if (entryPointFilePath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
return true;
// #! shebang 확인
try
{
using var stream = File.OpenRead(entryPointFilePath);
int first = stream.ReadByte();
int second = stream.ReadByte();
return first == '#' && second == '!';
}
catch
{
return false;
}
}
2. 캐시 확인
빌드가 필요한지 확인하는 로직:
- 전역 MSBuild 속성 비교
- 진입점 파일 변경 여부
- 암시적 파일들(global.json, NuGet.config 등) 변경 여부
- 새로운 암시적 빌드 파일 존재 여부
3. 가상 프로젝트 생성
WriteProjectFile() 메서드가 핵심 역할을 담당:
SDK 처리
<!-- 기본 SDK -->
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<!-- 추가 SDK들 -->
<Import Project="Sdk.props" Sdk="Aspire.AppHost.Sdk" Version="9.3.0" />
기본 속성들
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
파일 기반 프로그램 기능 활성화
<PropertyGroup>
<Features>$(Features);FileBasedProgram</Features>
</PropertyGroup>
4. MSBuild 실행
- Restore 실행
- Build 실행
- 캐시 항목 저장
가상 프로젝트 예제
단일 파일:
#!/program
#:sdk Microsoft.NET.Sdk
#:sdk Aspire.Hosting.Sdk@9.1.0
#:property TargetFramework=net11.0
#:package System.CommandLine@2.0.0-beta4.22272.1
#:property LangVersion=preview
Console.WriteLine();
생성되는 가상 프로젝트:
<Project>
<PropertyGroup>
<IncludeProjectNameInArtifactsPaths>false</IncludeProjectNameInArtifactsPaths>
<ArtifactsPath>/artifacts</ArtifactsPath>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<Import Project="Sdk.props" Sdk="Aspire.Hosting.Sdk" Version="9.1.0" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
</PropertyGroup>
<!-- 사용자 정의 속성들 -->
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="/path/to/Program.cs" />
</ItemGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
<Import Project="Sdk.targets" Sdk="Aspire.Hosting.Sdk" Version="9.1.0" />
</Project>
Part 3: C# 14 확장 멤버 - 모든 것의 확장
배경
C# 3.0에서 도입된 확장 메서드는 LINQ를 지원하기 위한 기능이었지만, 개발자들은 오랫동안 확장 속성, 정적 확장 메서드 등 "확장 모든 것"을 요구해왔습니다. C# 14에서 드디어 이 기능이 "확장 멤버"라는 이름으로 도입되었습니다.
새로운 확장 멤버 문법
기존 확장 메서드에서 변환
기존 문법:
public static class EnumerableExtensions
{
public static bool IsEmpty<T>(this IEnumerable<T> target)
=> !target.Any();
}
새로운 확장 멤버 문법:
public static class EnumerableExtensions
{
extension<T>(IEnumerable<T> target)
{
public bool IsEmpty() => !target.Any();
}
}
변환 과정
extension(){ }블록으로 메서드 감싸기- 수신자 매개변수를 extension 블록으로 이동
- 제네릭 타입 인수를 extension 블록으로 이동
- 메서드에서
static수정자 제거
새로운 확장 멤버 유형
1. 정적 확장 메서드
public static class StringExtensions
{
extension(string) // 매개변수 이름 불필요
{
public static bool HasValue(string value)
=> !string.IsNullOrEmpty(value);
}
}
// 사용법
if (string.HasValue(someValue)) // 타입에서 직접 호출
{
Console.WriteLine("The value was: " + someValue);
}
2. 인스턴스 확장 속성
public static class StringExtensions
{
extension(string target)
{
public bool IsAscii
=> target.All(x => char.IsAscii(x));
}
}
// 사용법
string someValue = "something";
bool isAscii = someValue.IsAscii; // 인스턴스에서 속성 접근
3. 정적 확장 속성
public static class StringExtensions
{
extension(string)
{
public static bool SomeStaticProperty { get; set; }
}
}
4. 확장 연산자 (Preview 7 예정)
static class PathExtensions
{
extension(string)
{
public static string operator /(string left, string right)
=> Path.Combine(left, right);
}
}
// 사용법
var fullPath = "part1" / "part2" / "test.txt";
직접 호출 방법 (모호성 해결)
// 인스턴스 확장 메서드
bool isEmpty = EnumerableExtensions.IsEmpty([]);
// 정적 확장 메서드
bool hasValue = StringExtensions.HasValue("something");
// 인스턴스 확장 속성
bool isAscii = StringExtensions.get_IsAscii("something");
실제 활용 사례: NetEscapades.EnumGenerators
C# 14 지원 전:
var colour = MyColoursExtensions.Parse("Red"); // 클래스를 통해 호출
C# 14 지원 후:
var colour = MyColours.Parse("Red"); // 열거형에서 직접 호출
생성된 코드:
public static partial class MyColoursExtensions
{
// 기존 확장 메서드들
public static string ToStringFast(this global::MyColours value) { }
public static int AsUnderlyingType(this global::MyColours value) { }
// C#14용 확장 블록
extension(global::MyColours)
{
public static bool IsDefined(global::MyColours value) { }
public static global::MyColours Parse(string? name) { }
}
}
Part 4: 소스 생성기의 ‘마커 특성’ 문제 해결
문제 정의
소스 생성기에서 마커 특성을 사용할 때 발생하는 CS0436 경고 문제:
[Generator]
public class HelloWorldGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(i =>
{
i.AddSource("MyExampleAttribute.g.cs", @"
namespace HelloWorld
{
internal class MyExampleAttribute: global::System.Attribute {}
}");
});
}
}
문제 상황:
- 여러 프로젝트에서 같은 생성기 사용
- 한 프로젝트가 다른 프로젝트 참조
[InternalsVisibleTo]사용
결과: 동일한 타입이 여러 프로젝트에 정의되어 충돌
기존 해결책: 공유 DLL
마커 특성을 별도 DLL에 포함하여 NuGet 패키지에 패키징:
장점:
- 모든 프로젝트가 동일한 타입 참조
- 타입 충돌 없음
단점:
- 빌드 복잡성 증가
- 별도 프로젝트 필요
- MSBuild 설정 복잡
.NET 10의 새로운 해결책: [Embedded] 특성
Embedded 특성 정의
namespace Microsoft.CodeAnalysis
{
internal sealed partial class EmbeddedAttribute : global::System.Attribute
{
}
}
요구사항
internal접근 수준class여야 함sealed여야 함- 비
static internal또는public매개변수 없는 생성자System.Attribute에서 상속- 모든 타입 선언에서 허용
사용법
[Generator]
public class HelloWorldGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context
.AddEmbeddedAttributeDefinition() // ← 추가
.RegisterPostInitializationOutput(i =>
{
i.AddSource("MyExampleAttribute.g.cs", @"
namespace HelloWorld
{
[global::Microsoft.CodeAnalysis.EmbeddedAttribute] // ← 추가
internal class MyExampleAttribute: global::System.Attribute {}
}");
});
}
}
장단점 비교
AddEmbeddedAttributeDefinition() 사용 시기
장점:
- 구현 간단
- 추가 프로젝트 불필요
- 타입 충돌 해결
단점:
- .NET 10 SDK 필요 (Roslyn 4.14+)
- Visual Studio 17.14+ 필요
- 기존 생성기에는 파괴적 변경
공유 DLL 계속 사용 시기
- 이미 공유 DLL 방식 구현됨
- 추가 기능이 필요 (공개 API 타입 등)
- 구버전 SDK 지원 필요
혼합 접근법
// 마커 특성은 [Embedded]로 생성
extension(string)
{
[Embedded]
internal class MyMarkerAttribute : Attribute { }
}
// 공개 API 타입은 공유 DLL에 포함
public enum TransformType
{
LowerInvariant,
UpperInvariant
}
Part 5: dnx를 통한 일회성 .NET 도구 실행
개요
Node.js의 npx와 유사한 기능으로, .NET 도구를 설치하지 않고 직접 실행할 수 있는 dnx 명령이 .NET 10 Preview 6에 추가되었습니다.
기본 사용법
도구 실행
# 처음 실행 시 확인 프롬프트
> dnx dotnetsay
Tool package dotnetsay@2.1.7 will be downloaded from source https://api.nuget.org/v3/index.json.
Proceed? [y/n] (y): y
Welcome to using a .NET Core global tool!
# 두 번째 실행부터는 즉시 실행
> dnx dotnetsay
Welcome to using a .NET Core global tool!
버전 지정
# @ 구분자로 버전 지정
dnx dotnetsay@2.1.7
dnx SomePackage@*
dnx SomePackage@9.*
지원 옵션
dnx --help
Usage:
dotnet dnx <packageId> [<commandArguments>...] [options]
Options:
--version <VERSION> 도구 패키지 버전
-y, --yes 모든 확인 프롬프트를 "yes"로 자동 응답
--interactive 사용자 입력 대기 허용
--allow-roll-forward 새 .NET 런타임 버전으로 롤포워드 허용
--prerelease 프리릴리스 패키지 포함
--configfile <FILE> 사용할 NuGet 구성 파일
--source <SOURCE> 설치 중 사용할 NuGet 패키지 소스
--add-source <ADDSOURCE> 추가 NuGet 패키지 소스
dotnet tool install과의 차이점
전통적인 전역 설치
# 전역 설치
dotnet tool install -g dotnetsay
# 설치 위치: ~/.dotnet/tools/
# 실행: 직접 명령어로 실행 가능
dotnetsay
dnx 방식
# 일회성 실행
dnx dotnetsay
# 다운로드 위치: 전역 패키지 캐시
# 도구 저장소나 shim 생성 안함
# 패키지 캐시에서 직접 실행
내부 구현
독립 실행 파일
- Windows:
C:\Program Files\dotnet\dnx.cmd - Linux/macOS:
/usr/share/dotnet/dnx
dnx.cmd 내용:
@echo off
"%~dp0dotnet.exe" dnx %*
Unix 스크립트:
#!/bin/sh
"$(dirname "$0")/dotnet" dnx "$@"
처리 단계
- 로컬 도구 매니페스트 확인: 버전이 지정되지 않은 경우
dotnet-tools.json확인 - 패키지 검색: nuget.org에서 패키지 검색
- 권한 확인: 다운로드 권한 요청 (–yes로 생략 가능)
- 도구 실행: 다운로드 후 실행
활용 시나리오
- 개발 중 빠른 테스트: 도구를 영구 설치하지 않고 테스트
- CI/CD 파이프라인: 일회성 도구 사용
- 스크립팅: 도구 설치 없이 스크립트에서 활용
- 탐색적 사용: 새로운 도구 시도
Part 6: ASP.NET Core Identity의 Passkey 지원
Passkey 개요
Passkey는 비밀번호 없는 인증 방식으로, FIDO(Fast IDentity Online) 표준을 기반으로 합니다:
- 보안: 피싱 공격에 면역
- 편의성: 생체인식이나 PIN으로 인증
- 표준화: 웹 표준 WebAuthn 기반
.NET 10에서의 제한사항
중요: 현재 구현은 여전히 비밀번호를 요구합니다.
- 계정 생성 시 비밀번호 필수
- Passkey는 추가 인증 수단으로만 사용
- 진정한 “비밀번호 없는” 환경은 아님
새로운 템플릿 경험
프로젝트 생성
dotnet new blazor -au Individual
사용자 등록
- 일반적인 비밀번호 기반 등록
- 이메일 확인
- 로그인 후 계정 관리 페이지로 이동
Passkey 추가
- 계정 관리 → Passkeys 섹션
- “Add a new passkey” 클릭
- 브라우저 네이티브 다이얼로그
- 생체인식/PIN 인증
- Passkey 이름 지정
Passkey 로그인
- 로그인 페이지에서 “Log in with a passkey” 클릭
- 브라우저에서 저장된 Passkey 선택
- 생체인식/PIN 인증
- 즉시 로그인 완료
기술적 구현
핵심 컴포넌트
PasskeySubmit.razor:
<button type="submit" name="__passkeySubmit" @attributes="AdditionalAttributes">
@ChildContent
</button>
<passkey-submit operation="@Operation" name="@Name" email-name="@EmailName">
</passkey-submit>
JavaScript 구현 (PasskeySubmit.razor.js)
customElements.define('passkey-submit', class extends HTMLElement {
static formAssociated = true;
connectedCallback() {
this.internals = this.attachInternals();
this.attrs = {
operation: this.getAttribute('operation'),
name: this.getAttribute('name'),
emailName: this.getAttribute('email-name'),
};
// 폼 제출 이벤트 처리
this.internals.form.addEventListener('submit', (event) => {
if (event.submitter?.name === '__passkeySubmit') {
event.preventDefault();
this.obtainCredentialAndSubmit();
}
});
// 자동 완성 시도
this.tryAutofillPasskey();
}
async obtainCredentialAndSubmit(useConditionalMediation = false) {
let credential;
if (this.attrs.operation === 'Create') {
credential = await createCredential(signal);
} else if (this.attrs.operation === 'Request') {
const email = new FormData(this.internals.form).get(this.attrs.emailName);
const mediation = useConditionalMediation ? 'conditional' : undefined;
credential = await requestCredential(email, mediation, signal);
}
// 자격 증명을 JSON으로 변환하여 폼에 추가
const credentialJson = JSON.stringify(credential);
formData.append(`${this.attrs.name}.CredentialJson`, credentialJson);
this.internals.setFormValue(formData);
this.internals.form.submit();
}
});
WebAuthn API 호출
Passkey 생성:
async function createCredential(signal) {
// ASP.NET Core Identity 엔드포인트 호출
const optionsResponse = await fetchWithErrorHandling('/Account/PasskeyCreationOptions', {
method: 'POST',
signal,
});
const optionsJson = await optionsResponse.json();
const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
// 브라우저 WebAuthn API 호출
return await navigator.credentials.create({ publicKey: options, signal });
}
Passkey 인증:
async function requestCredential(email, mediation, signal) {
const optionsResponse = await fetchWithErrorHandling(
`/Account/PasskeyRequestOptions?username=${email}`, {
method: 'POST',
signal,
});
const optionsJson = await optionsResponse.json();
const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
return await navigator.credentials.get({ publicKey: options, mediation, signal });
}
백엔드 API 엔드포인트
Passkey 생성 옵션:
accountGroup.MapPost("/PasskeyCreationOptions", async (
HttpContext context,
[FromServices] UserManager<ApplicationUser> userManager,
[FromServices] SignInManager<ApplicationUser> signInManager) =>
{
var user = await userManager.GetUserAsync(context.User);
var userId = await userManager.GetUserIdAsync(user);
var userName = await userManager.GetUserNameAsync(user) ?? "User";
var userEntity = new PasskeyUserEntity(userId, userName, displayName: userName);
var passkeyCreationArgs = new PasskeyCreationArgs(userEntity);
var options = await signInManager.ConfigurePasskeyCreationOptionsAsync(passkeyCreationArgs);
return TypedResults.Content(options.AsJson(), contentType: "application/json");
});
데이터베이스 스키마
CREATE TABLE AspNetUserPasskeys (
CredentialId BLOB(1024) NOT NULL PRIMARY KEY,
UserId TEXT NOT NULL,
Data TEXT NOT NULL,
FOREIGN KEY (UserId) REFERENCES AspNetUsers (Id) ON DELETE CASCADE
);
주요 특징
- 조건부 중재: 자동 완성 지원
- 크로스 디바이스: 다른 기기의 Passkey 사용 가능
- 다중 Passkey: 여러 디바이스에 Passkey 등록 가능
- 관리 기능: Passkey 이름 변경, 삭제
한계점
- 비밀번호 여전히 필요: 진정한 비밀번호 없는 환경 아님
- 프리뷰 기능: API 변경 가능성
- 제한된 WebAuthn 구현: 전체 FIDO2 라이브러리 아님
- Attestation 검증 없음: 확장성 포인트로 제3자 라이브러리 사용 필요
Part 7: 자체 포함 및 Native AOT .NET 도구의 NuGet 패키징
개요
.NET 10 Preview 6부터 .NET 도구를 다양한 배포 모델로 패키징할 수 있게 되었습니다:
- 프레임워크 종속, 플랫폼 무관 (기존 방식)
- 프레임워크 종속, 플랫폼 특정
- 자체 포함, 플랫폼 특정
- 자체 포함, 트림됨
- Native AOT 컴파일됨
샘플 애플리케이션
테스트용 도구 설정:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<PackAsTool>true</PackAsTool>
<ToolCommandName>sayhello</ToolCommandName>
<PackageId>sayhello</PackageId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Spectre.Console" Version="0.50.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.8" />
</ItemGroup>
</Project>
1. 프레임워크 종속, 플랫폼 무관 (기존 방식)
dotnet pack -o ./artifacts/packages/agnostic
특징:
- 패키지 크기: ~91MB (대용량)
- 모든 대상 프레임워크 포함
- 모든 플랫폼용 네이티브 파일 포함
- .NET 런타임 설치 필요
DotnetToolSettings.xml:
<?xml version="1.0" encoding="utf-8"?>
<DotNetCliTool Version="1">
<Commands>
<Command Name="sayhello" EntryPoint="MultiRid.dll" Runner="dotnet" />
</Commands>
</DotNetCliTool>
2. 프레임워크 종속, 플랫폼 특정
<PropertyGroup>
<RuntimeIdentifiers>linux-x64;linux-arm64;win-x64;win-arm64;any</RuntimeIdentifiers>
<PublishSelfContained>false</PublishSelfContained>
</PropertyGroup>
dotnet pack -o ./artifacts/packages/specific
결과: 6개 패키지 생성
sayhello.1.0.0.nupkg(루트 패키지)sayhello.linux-x64.1.0.0.nupkgsayhello.linux-arm64.1.0.0.nupkgsayhello.win-x64.1.0.0.nupkgsayhello.win-arm64.1.0.0.nupkgsayhello.any.1.0.0.nupkg(폴백)
루트 패키지 DotnetToolSettings.xml:
<?xml version="1.0" encoding="utf-8"?>
<DotNetCliTool Version="2">
<Commands>
<Command Name="sayhello" />
</Commands>
<RuntimeIdentifierPackages>
<RuntimeIdentifierPackage RuntimeIdentifier="linux-x64" Id="sayhello.linux-x64" />
<RuntimeIdentifierPackage RuntimeIdentifier="linux-arm64" Id="sayhello.linux-arm64" />
<RuntimeIdentifierPackage RuntimeIdentifier="win-x64" Id="sayhello.win-x64" />
<RuntimeIdentifierPackage RuntimeIdentifier="win-arm64" Id="sayhello.win-arm64" />
<RuntimeIdentifierPackage RuntimeIdentifier="any" Id="sayhello.any" />
</RuntimeIdentifierPackages>
</DotNetCliTool>
플랫폼별 패키지 DotnetToolSettings.xml:
<?xml version="1.0" encoding="utf-8"?>
<DotNetCliTool Version="2">
<Commands>
<Command Name="sayhello" EntryPoint="MultiRid" Runner="executable" />
</Commands>
</DotNetCliTool>
3. 자체 포함, 플랫폼 특정
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RuntimeIdentifiers>linux-x64;linux-arm64;win-x64;win-arm64</RuntimeIdentifiers>
<PublishSelfContained>true</PublishSelfContained>
</PropertyGroup>
특징:
- .NET 런타임 포함
- 단일 타겟 프레임워크
- 패키지 크기: 각각 ~30MB
- 대상 머신에 .NET 설치 불필요
4. 자체 포함, 트림됨
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RuntimeIdentifiers>linux-x64;linux-arm64;win-x64;win-arm64</RuntimeIdentifiers>
<PublishSelfContained>true</PublishSelfContained>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
특징:
- 미사용 코드 제거
- 패키지 크기: 각각 ~10MB
- 트리밍 호환성 고려 필요
5. Native AOT
<PropertyGroup>
<RuntimeIdentifiers>linux-x64;linux-arm64;win-x64;win-arm64</RuntimeIdentifiers>
<PublishSelfContained>true</PublishSelfContained>
<PublishTrimmed>true</PublishTrimmed>
<PublishAot>true</PublishAot>
<StripSymbols>true</StripSymbols>
</PropertyGroup>
빌드 과정:
# 루트 패키지
dotnet pack -o ./artifacts/packages/nativeoat
# 각 런타임별로 개별 빌드 필요
dotnet pack -o ./artifacts/packages/nativeaot --runtime-id win-x64
dotnet pack -o ./artifacts/packages/nativeaot --runtime-id linux-x64
# ... 다른 런타임들
특징:
- 패키지 크기: 각각 ~2.5MB (최소)
- 빠른 시작 시간
- 작은 메모리 풋프린트
- 플랫폼별로 개별 빌드 필요
패키지 크기 비교
| 배포 모델 | 패키지 크기 | 특징 |
|---|---|---|
| 플랫폼 무관 | ~91MB | 모든 플랫폼, 모든 프레임워크 |
| 플랫폼 특정 | ~20MB 각각 | 단일 플랫폼, 모든 프레임워크 |
| 자체 포함 | ~30MB 각각 | 런타임 포함 |
| 트림됨 | ~10MB 각각 | 미사용 코드 제거 |
| Native AOT | ~2.5MB 각각 | 최적화된 네이티브 코드 |
제한사항 및 권장사항
현재 제한사항
- .NET 10 SDK 필요: Version 2 도구는 .NET 10 SDK에서만 설치 가능
- any 폴백 버그: RC2에서 수정 예정
- Native AOT 호환성: 모든 라이브러리가 지원되지 않음
권장사항
플랫폼 특정 도구를 사용해야 하는 경우:
- 네이티브 종속성이 있는 경우
- 다운로드 크기가 중요한 경우
- 최신 .NET SDK 사용 가능한 환경
기존 방식을 유지해야 하는 경우:
- 광범위한 호환성 필요
- 구버전 .NET SDK 지원 필요
- 안정성이 중요한 프로덕션 환경
마이그레이션 전략
<!-- 점진적 전환: 두 방식 모두 지원 -->
<PropertyGroup Condition="'$(UseNewPackaging)' == 'true'">
<RuntimeIdentifiers>linux-x64;linux-arm64;win-x64;win-arm64;any</RuntimeIdentifiers>
<PublishSelfContained>true</PublishSelfContained>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
<PropertyGroup Condition="'$(UseNewPackaging)' != 'true'">
<TargetFrameworks>netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
CI/CD 자동화 예제
# GitHub Actions 예제
- name: Pack platform-specific tools
run: |
dotnet pack --configuration Release --output ./packages/
for rid in linux-x64 linux-arm64 win-x64 win-arm64; do
dotnet pack --configuration Release --runtime-id $rid --output ./packages/
done
시리즈 총정리
주요 테마
.NET 10 Preview 시리즈는 다음과 같은 주요 개선사항들을 다룹니다:
- 개발자 경험 향상: 단일 파일 실행, dnx 도구
- 언어 기능 확장: C# 14 확장 멤버
- 도구 생태계 개선: 소스 생성기 문제 해결
- 보안 강화: Passkey 지원
- 배포 옵션 확대: 다양한 .NET 도구 패키징 방식
개발자에게 미치는 영향
입문자
- 더 낮은 진입 장벽: 프로젝트 파일 없이 C# 학습 가능
- 즉시 실행: 복잡한 설정 없이 코드 실행
라이브러리 개발자
- 향상된 API 설계: 확장 멤버로 더 직관적인 API
- 소스 생성기 안정성: [Embedded] 특성으로 타입 충돌 해결
도구 개발자
- 유연한 배포: Native AOT, 트림 등 다양한 옵션
- 향상된 사용자 경험: dnx로 설치 없는 도구 사용
웹 개발자
- 현대적 인증: Passkey 지원으로 보안 강화
- 개발 편의성: 향상된 템플릿과 도구
미래 전망
.NET 10은 다음과 같은 방향으로 발전하고 있습니다:
- 단순성 추구: 복잡한 설정 없이 즉시 사용 가능
- 성능 최적화: Native AOT, 트리밍 등으로 더 빠르고 작은 애플리케이션
- 현대적 표준 지원: Passkey, WebAuthn 등 최신 웹 표준
- 개발자 생산성: 도구 간소화, 자동화 증진
- 생태계 통합: 다른 플랫폼의 좋은 아이디어 수용 (npx → dnx)
이러한 변화들은 .NET을 더욱 접근하기 쉽고, 성능이 뛰어나며, 현대적인 개발 플랫폼으로 만들어가고 있습니다.