.NET 9의 새로운 기능

.NET 9의 새로운 기능에 대해 알아보고 추가 설명서에 대한 링크를 찾아보세요.

.NET 8의 후속 버전인 .NET 9는 클라우드 기반 앱과 성능에 특별히 중점을 두고 있습니다. STS(표준 기간 지원) 릴리스로 18개월 동안 지원됩니다. 여기에서 .NET 9를 다운로드할 수 있습니다.

.NET 9의 새로운 기능인 엔지니어링 팀은 GitHub 토론에 .NET 9 미리 보기 업데이트를 게시합니다. 이 곳에서 질문을 하고 릴리스에 대한 피드백을 제공할 수 있습니다.

이 문서는 .NET 9 미리 보기 2용으로 업데이트되었습니다. 다음 섹션에서는 .NET 9의 핵심 .NET 라이브러리에 대한 업데이트를 설명합니다.

‘.NET 런타임’

직렬화

System.Text.Json에서 .NET 9에는 JSON 직렬화를 위한 새로운 옵션과 웹 기본값을 사용하여 더 쉽게 직렬화할 수 있는 새로운 싱글톤이 있습니다.

들여쓰기 옵션

JsonSerializerOptions에는 작성된 JSON의 들여쓰기 문자와 들여쓰기 크기를 사용자 지정할 수 있는 새로운 속성이 포함되어 있습니다.

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    IndentCharacter = '\t',
    IndentSize = 2,
};

string json = JsonSerializer.Serialize(
    new { Value = 1 },
    options
    );
Console.WriteLine(json);
//{
//                "Value": 1
//}

기본 웹 옵션

웹앱에 대해 ASP.NET Core가 사용하는 기본 옵션으로 직렬화하려면 새로운 JsonSerializerOptions.Web 싱글톤을 사용합니다.

string webJson = JsonSerializer.Serialize(
    new { SomeValue = 42 },
    JsonSerializerOptions.Web // Defaults to camelCase naming policy.
    );
Console.WriteLine(webJson);
// {"someValue":42}

LINQ

새로운 메서드 CountByAggregateBy가 도입되었습니다. 이러한 메서드를 사용하면 GroupBy를 통해 중간 그룹화를 할당할 필요 없이 키별로 상태를 집계할 수 있습니다.

CountBy를 사용하면 각 키의 빈도를 빠르게 계산할 수 있습니다. 다음 예에서는 텍스트 문자열에서 가장 자주 나타나는 단어를 찾습니다.

string sourceText = """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    Sed non risus. Suspendisse lectus tortor, dignissim sit amet, 
    adipiscing nec, ultricies sed, dolor. Cras elementum ultrices amet diam.
""";

// Find the most frequent word in the text.
KeyValuePair<string, int> mostFrequentWord = sourceText
    .Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries)
    .Select(word => word.ToLowerInvariant())
    .CountBy(word => word)
    .MaxBy(pair => pair.Value);

Console.WriteLine(mostFrequentWord.Key); // amet

AggregateBy를 사용하면 보다 일반적인 목적의 워크플로를 구현할 수 있습니다. 다음 예에서는 지정된 키와 연관된 점수를 계산하는 방법을 보여 줍니다.

(string id, int score)[] data =
    [
        ("0", 42),
        ("1", 5),
        ("2", 4),
        ("1", 10),
        ("0", 25),
    ];

var aggregatedData =
    data.AggregateBy(
        keySelector: entry => entry.id,
        seed: 0,
        (totalScore, curr) => totalScore + curr.score
        );

foreach (var item in aggregatedData)
{
    Console.WriteLine(item);
}
//(0, 67)
//(1, 15)
//(2, 4)

Index<TSource>(IEnumerable<TSource>)를 사용하면 열거형의 암시적 인덱스를 빠르게 추출할 수 있습니다. 이제 다음 코드 조각과 같은 코드를 작성하여 컬렉션의 항목을 자동으로 인덱싱할 수 있습니다.

IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
    Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}

컬렉션

PriorityQueue<TElement,TPriority> 네임스페이스의 System.Collections.Generic 컬렉션 형식에는 큐에 있는 항목의 우선 순위를 업데이트하는 데 사용할 수 있는 새 Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) 메서드가 포함되어 있습니다.

PriorityQueue.Remove() 메서드

.NET 6에는 간단하고 빠른 배열 힙 구현을 제공하는 PriorityQueue<TElement,TPriority> 컬렉션이 도입되었습니다. 일반적으로 배열 힙과 관련된 한 가지 문제는 우선 순위 업데이트를 지원하지 않음으로 인해 Dijkstra 알고리즘의 변형과 같은 알고리즘에서 사용이 금지된다는 것입니다.

기존 컬렉션에서 효율적인 $O(\log n)$ 우선 순위 업데이트를 구현하는 것은 불가능하지만, 새로운 PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) 메서드를 사용하면 우선 순위 업데이트를 에뮬레이트할 수 있습니다($O(n)$ 시간에도 불구하고).

public static void UpdatePriority<TElement, TPriority>(
    this PriorityQueue<TElement, TPriority> queue,
    TElement element,
    TPriority priority
    )
{
    // Scan the heap for entries matching the current element.
    queue.Remove(element, out _, out _);
    // Re-insert the entry with the new priority.
    queue.Enqueue(element, priority);
}

이 방법은 점근적 성능이 차단 요인이 아닌 상황에서 그래프 알고리즘을 구현하려는 사용자의 차단을 해제합니다. (이러한 컨텍스트에는 교육 및 프로토타입 제작이 포함됩니다.) 예를 들어, 다음은 새 API를 사용하는 Dijkstra 알고리즘의 장난감 구현입니다.

암호화

암호화의 경우 .NET 9는 CryptographicOperations 형식에 새로운 원샷 해시 방법을 추가합니다. 또한 KMAC 알고리즘을 사용하는 새 클래스를 추가합니다.

CryptographicOperations.HashData() 메서드

.NET에는 해시 함수 및 관련 함수의 여러 가지 정적 "원샷" 구현이 포함되어 있습니다. 이러한 API에는 SHA256.HashDataHMACSHA256.HashData가 포함됩니다. 원샷 API는 최상의 성능을 제공하고 할당을 줄이거나 제거할 수 있으므로 사용하는 것이 좋습니다.

개발자가 호출자가 사용할 해시 알고리즘을 정의하는 해싱을 지원하는 API를 제공하려는 경우 일반적으로 HashAlgorithmName 인수를 허용하여 수행됩니다. 그러나 일회성 API와 함께 해당 패턴을 사용하려면 가능한 모든 HashAlgorithmName을 전환한 다음 적절한 방법을 사용해야 합니다. 이 문제를 해결하기 위해 .NET 9에는 CryptographicOperations.HashData API가 도입되었습니다. 이 API를 사용하면 사용된 알고리즘이 HashAlgorithmName에 의해 결정되는 일회성으로 입력에 대한 해시 또는 HMAC를 생성할 수 있습니다.

static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
    byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
    ProcessHash(hash);
}

KMAC 알고리즘

.NET 9는 NIST SP-800-185에 지정된 대로 KMAC 알고리즘을 제공합니다. KMAC(KECCAK 메시지 인증 코드)는 KECCAK를 기반으로 하는 의사 난수 함수 및 키 해시 함수입니다.

다음 새 클래스는 KMAC 알고리즘을 사용합니다. 인스턴스를 사용해 데이터를 누적하여 MAC을 생성하거나 단일 입력에 대한 원샷을 위해 정적 HashData 메서드를 사용합니다.

KMAC는 OpenSSL 3.0 이상이 설치된 Linux와 Windows 11 Build 26016 이상에서 사용할 수 있습니다. 정적 IsSupported 속성을 사용하여 플랫폼이 원하는 알고리즘을 지원하는지 확인할 수 있습니다.

if (Kmac128.IsSupported)
{
    byte[] key = GetKmacKey();
    byte[] input = GetInputToMac();
    byte[] mac = Kmac128.HashData(key, input, outputLength: 32);
}
else
{
    // Handle scenario where KMAC isn't available.
}

반영

.NET Core 버전 및 .NET 5-8에서는 어셈블리 빌드 및 동적으로 만들어진 형식에 대한 리플렉션 메타데이터 내보내기에 대한 지원이 실행 가능한 AssemblyBuilder로 제한되었습니다. 어셈블리 저장에 대한 지원 부족이 .NET Framework에서 .NET으로 마이그레이션하는 고객을 방해하는 경우가 많았습니다. .NET 9에서는 내보낸 어셈블리를 저장하기 위해 공용 API를 AssemblyBuilder에 추가합니다.

새로운 지속형 AssemblyBuilder 구현은 런타임 및 플랫폼 독립적입니다. 지속형 AssemblyBuilder 인스턴스를 만들려면 새로운 AssemblyBuilder.DefinePersistedAssembly API를 사용합니다. 기존 AssemblyBuilder.DefineDynamicAssembly API는 어셈블리 이름과 선택적 사용자 지정 특성을 허용합니다. 새 API를 사용하려면 기본 런타임 형식을 참조하는 데 사용되는 코어 어셈블리 System.Private.CoreLib를 전달합니다. AssemblyBuilderAccess에 대한 옵션이 없습니다. 현재로서는 지속형 AssemblyBuilder 구현은 실행이 아닌 저장만 지원합니다. 지속형 AssemblyBuilder의 인스턴스를 만든 후 모듈, 형식, 메서드 또는 열거형 정의, IL 작성 및 기타 모든 사용법을 위한 후속 단계는 변경되지 않습니다. 즉, 어셈블리를 저장하기 위해 기존 System.Reflection.Emit 코드를 있는 그대로 사용할 수 있습니다. 다음은 예를 보여 주는 코드입니다.

public void CreateAndSaveAssembly(string assemblyPath)
{
    AssemblyBuilder ab = AssemblyBuilder.DefinePersistedAssembly(
        new AssemblyName("MyAssembly"),
        typeof(object).Assembly
        );
    TypeBuilder tb = ab.DefineDynamicModule("MyModule")
        .DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);

    MethodBuilder mb = tb.DefineMethod(
        "SumMethod",
        MethodAttributes.Public | MethodAttributes.Static,
        typeof(int), [typeof(int), typeof(int)]
        );
    ILGenerator il = mb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    tb.CreateType();
    ab.Save(assemblyPath); // or could save to a Stream
}

public void UseAssembly(string assemblyPath)
{
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    Type type = assembly.GetType("MyType");
    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, [5, 10]));
}

성능

.NET 9에는 앱 성능 향상을 위한 64비트 JIT 컴파일러의 향상된 기능이 포함되어 있습니다. 이러한 컴파일러의 향상된 기능은 다음과 같습니다.

Arm64 벡터화 는 런타임의 또 다른 새로운 기능입니다.

루프 최적화

루프에 대한 코드 생성을 개선하는 것이 .NET 9의 우선 순위이며, 64비트 컴파일러는 IV(유도 변수) 확대라는 새로운 최적화 기능을 제공합니다.

IV는 포함하는 루프가 반복될 때 값이 변경되는 변수입니다. 다음 for 루프 i 에서는 IV for (int i = 0; i < 10; i++)입니다. 컴파일러가 루프 반복을 통해 IV 값이 어떻게 진화하는지 분석할 수 있는 경우 관련 식에 대해 더 성능이 뛰어난 코드를 생성할 수 있습니다.

배열을 반복하는 다음 예제를 살펴보겠습니다.

static int Sum(int[] arr)
{
    int sum = 0;
    for (int i = 0; i < arr.Length; i++)
    {
        sum += arr[i];
    }

    return sum;
}

인덱스 변수 i는 크기가 4바이트입니다. 어셈블리 수준에서 64비트 레지스터는 일반적으로 x64에 배열 인덱스를 보유하는 데 사용되며, 이전 .NET 버전에서는 컴파일러가 배열 액세스를 위해 8바이트까지 0으로 확장 i 되지만 다른 곳에서는 4바이트 정수로 계속 처리 i 되는 코드를 생성했습니다. 그러나 8바이트까지 확장 i 하려면 x64에 대한 추가 지침이 필요합니다. IV 확대를 사용하면 이제 64비트 JIT 컴파일러가 루프 전체에서 8바이트로 확장 i 되어 확장이 0이 생략됩니다. 배열을 반복하는 것은 매우 일반적이며 이 명령 제거의 이점은 빠르게 추가됩니다.

네이티브 AOT의 인라인 향상

중 하나입니다. 64비트 JIT 컴파일러의 인라이너에 대한 NET의 목표는 메서드가 가능한 한 많이 인라인되지 않도록 차단하는 제한을 제거하는 것입니다. .NET 9를 사용하면 Windows x64, Linux x64 및 Linux Arm64에서 스레드 로컬 정적에 대한 액세스를 인라인 처리할 수 있습니다.

클래스 멤버의 경우 static 멤버를 "공유"하는 클래스의 모든 인스턴스에 정확히 하나의 멤버 인스턴스가 있습니다. 멤버의 static 값이 각 스레드에 고유한 경우 해당 값을 스레드 로컬로 만들면 포함하는 스레드에서 멤버에 안전하게 액세스 static 하기 위한 동시성 기본 형식이 필요하지 않으므로 성능이 향상될 수 있습니다.

이전에는 네이티브 AOT 컴파일 프로그램에서 스레드 로컬 정적에 액세스하려면 64비트 JIT 컴파일러가 스레드 로컬 스토리지의 기본 주소를 가져오기 위해 런타임에 호출을 내보내야 했습니다. 이제 컴파일러가 이러한 호출을 인라인 처리하여 이 데이터에 액세스하는 지침이 훨씬 줄어듭니다.

PGO 개선 사항: 형식 검사 및 캐스트

.NET 8은 기본적으로 동적 프로필 기반 최적화(PGO) 를 사용하도록 설정했습니다. NET 9는 64비트 JIT 컴파일러의 PGO 구현을 확장하여 더 많은 코드 패턴을 프로파일러합니다. 계층화된 컴파일을 사용하도록 설정하면 64비트 JIT 컴파일러가 이미 프로그램에 계측을 삽입하여 해당 동작을 프로파일러합니다. 최적화를 사용하여 다시 컴파일하는 경우 컴파일러는 런타임에 빌드한 프로필을 활용하여 프로그램의 현재 실행과 관련된 결정을 내립니다. .NET 9에서 64비트 JIT 컴파일러는 PGO 데이터를 사용하여 검사 형식의 성능을 향상시킵니다.

개체의 형식을 확인하려면 런타임에 대한 호출이 필요하며 성능 저하가 발생합니다. 개체의 형식을 검사 필요한 경우 64비트 JIT 컴파일러는 정확성을 위해 이 호출을 내보낸다(컴파일러는 일반적으로 불가능해 보이는 경우에도 가능성을 배제할 수 없음). 그러나 PGO 데이터에 따라 개체가 특정 형식일 가능성이 있다고 제안하면 64비트 JIT 컴파일러는 이제 해당 형식에 대해 저렴하게 검사 빠른 경로를 내보내고 필요한 경우에만 런타임으로 호출하는 느린 경로로 대체됩니다.

.NET 라이브러리의 Arm64 벡터화

EncodeToUtf8 구현은 Arm64에서 다중 레지스터 부하/저장 지침을 내보내는 64비트 JIT 컴파일러의 기능을 활용합니다. 이 동작을 사용하면 프로그램에서 더 적은 명령으로 더 큰 데이터 청크를 처리할 수 있습니다. 다양한 작업기본 .NET 앱은 이러한 기능을 지원하는 Arm64 하드웨어에서 처리량 향상을 확인해야 합니다. 일부 벤치마크는 실행 시간을 절반 이상 줄였습니다.

.NET SDK

단위 테스트

이 섹션에서는 .NET 9의 단위 테스트 업데이트, 즉 병렬로 테스트 실행 및 터미널 로거 테스트 출력에 대해 설명합니다.

병렬로 테스트 실행

.NET 9에서는 dotnet test MSBuild와 완전히 통합됩니다. MSBuild는 병렬로 빌드를 지원하므로 여러 대상 프레임워크에서 동일한 프로젝트에 대한 테스트를 병렬로 실행할 수 있습니다. 기본적으로 MSBuild는 병렬 프로세스 수를 컴퓨터의 프로세서 수로 제한합니다. -maxcpucount 스위치를 사용하여 사용자 고유의 제한을 설정할 수도 있습니다. 병렬 처리를 옵트아웃하려면 MSBuild 속성을 false.로 설정합니다TestTfmsInParallel.

터미널 로거 테스트 표시

테스트 결과 보고 dotnet test 는 이제 MSBuild 터미널 로거에서 직접 지원됩니다. 테스트가 실행되는 동안(실행 중인 테스트 이름을 표시) 테스트가 완료된 후(모든 테스트 오류가 더 나은 방식으로 렌더링됨) 보다 완전한 기능을 갖춘 테스트 보고를 얻을 수 있습니다.

터미널 로거에 대한 자세한 내용은 dotnet 빌드 옵션을 참조 하세요.

.NET 도구 롤 포워드

.NET 도구 는 전역 또는 로컬로 설치한 다음 .NET SDK 및 설치된 .NET 런타임을 사용하여 실행할 수 있는 프레임워크 종속 앱입니다. 모든 .NET 앱과 같은 이러한 도구는 특정 주 버전의 .NET을 대상으로 합니다. 기본적으로 앱은 최신 버전의 .NET에서 실행되지 않습니다. 도구 작성자는 MSBuild 속성을 설정 RollForward 하여 최신 버전의 .NET 런타임에서 도구를 실행하도록 옵트인할 수 있었습니다. 그러나 모든 도구가 그렇게 하는 것은 아닙니다.

사용자가 .NET 도구를 실행하는 방법을 결정할 수 있는 새로운 옵션을 dotnet tool install 사용할 수 있습니다. 도구를 통해 dotnet tool install설치하거나 도구를 통해 dotnet tool run <toolname>실행할 때 라는 --allow-roll-forward새 플래그를 지정할 수 있습니다. 이 옵션은 롤 포워드 모드 Major로 도구를 구성합니다. 이 모드를 사용하면 일치하는 .NET 버전을 사용할 수 없는 경우 최신 주 버전의 .NET에서 도구를 실행할 수 있습니다. 이 기능을 사용하면 얼리어답터에서 도구 작성자가 코드를 변경하지 않고도 .NET 도구를 사용할 수 있습니다.

참고 항목