다음을 통해 공유


.NET 8 런타임의 새로운 기능

이 문서에서는 .NET 8용 .NET 런타임의 새로운 기능을 설명합니다.

성능 개선 사항

.NET 8에는 코드 생성 및 JIT(Just-In Time) 컴파일에 대한 개선 사항이 포함되어 있습니다.

  • Arm64 성능 개선
  • SIMD 개선
  • AVX-512 ISA 확장 지원(Vector512 및 AVX-512 참조)
  • 클라우드 네이티브 개선
  • JIT 처리량 개선
  • 루프 및 일반 최적화
  • ThreadStaticAttribute(으)로 표시된 필드에 대한 최적화된 액세스
  • 연속 레지스터 할당. Arm64에는 테이블 벡터 조회를 위한 두 가지 명령이 있으며, 이를 위해서는 튜플 피연산자의 모든 엔터티가 연속 레지스터에 있어야 합니다.
  • JIT/NativeAOT는 이제 컴파일 시간에 크기를 결정할 수 있는 경우 비교, 복사, 제로화 등 SIMD를 사용하여 일부 메모리 작업을 펼치고 자동 벡터화할 수 있습니다.

또한 동적 PGO(프로필 기반 최적화)가 개선되어 이제 기본적으로 사용하도록 설정됩니다. 이를 사용하도록 설정하기 위해 더 이상 런타임 구성 옵션을 사용할 필요가 없습니다. 동적 PGO는 계층화된 컴파일과 함께 작동하여 계층 0 동안 배치된 추가 계측을 기반으로 코드를 더욱 최적화합니다.

평균적으로 동적 PGO는 성능을 약 15% 향상시킵니다. 4,600개 이하의 테스트로 구성된 벤치마크 도구 모음에서 23%의 성능이 20% 이상 개선되었습니다.

Codegen 구조체 승격

.NET 8에는 JIT의 구조체 변수 승격 기능을 일반화하는 codegen에 대한 새로운 실제 승격 최적화 패스가 포함되어 있습니다. 이 최적화(집계의 스칼라 대체라고도 함)는 구조체 변수의 필드를 JIT가 더 정확하게 추론하고 최적화할 수 있는 기본 변수로 바꿉니다.

JIT는 이미 이 최적화를 지원했지만 다음을 포함하여 몇 가지 큰 제한 사항이 있었습니다.

  • 필드가 4개 이하인 구조체에만 지원되었습니다.
  • 각 필드가 기본 형식이거나 기본 형식을 래핑하는 간단한 구조체인 경우에만 지원되었습니다.

실제 프로모션은 이러한 제한 사항을 제거하여 오랫동안 지속된 여러 JIT 문제를 해결합니다.

가비지 수집

.NET 8에는 즉시 메모리 제한을 조정하는 기능이 추가되었습니다. 이는 수요가 들어오고 나가는 클라우드 서비스 시나리오에 유용합니다. 비용 효율성을 높이려면 서비스는 수요 변동에 따라 리소스 사용량을 스케일 업하거나 스케일 다운합니다. 서비스가 수요 감소를 검색하면 메모리 제한을 줄여 리소스 사용량을 스케일 다운할 수 있습니다. 이전에는 GC(가비지 수집기)가 변경 내용을 인식하지 못하고 새 제한보다 더 많은 메모리를 할당할 수 있었기 때문에 이 작업이 실패했습니다. 이 변경으로 인해 RefreshMemoryLimit() API를 호출하여 GC를 새로운 메모리 제한으로 업데이트할 수 있습니다.

알아야 할 몇 가지 제한 사항이 있습니다.

  • 32비트 플랫폼(예: Windows x86 및 Linux ARM)에서 .NET은 아직 새로운 힙 하드 제한이 없으면 이를 설정할 수 없습니다.
  • API는 새로 고침이 실패했음을 나타내는 0이 아닌 상태 코드를 반환할 수 있습니다. 이는 스케일 다운이 너무 공격적이어서 GC가 조작할 여지가 없는 경우 발생할 수 있습니다. 이 경우, 현재 메모리 사용량을 줄이기 위해 GC.Collect(2, GCCollectionMode.Aggressive) 호출을 고려한 후 다시 시도합니다.
  • GC가 시작 중에 프로세스가 처리할 수 있다고 생각하는 크기 이상으로 메모리 제한을 스케일 업하면 RefreshMemoryLimit 호출은 성공하지만 제한으로 인식되는 것보다 더 많은 메모리를 사용할 수는 없습니다.

다음 코드 조각은 API를 호출하는 방법을 보여 줍니다.

GC.RefreshMemoryLimit();

메모리 제한과 관련된 일부 GC 구성 설정을 새로 고칠 수도 있습니다. 다음 코드 조각은 힙 하드 제한을 100MiB로 설정합니다.

AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();

API는 하드 제한이 유효하지 않은 경우(예: 음수 힙 하드 제한 비율의 경우 및 하드 제한이 너무 낮은 경우) InvalidOperationException을(를) 발생시킬 수 있습니다. 이는 새로운 AppData 설정으로 인해 또는 컨테이너 메모리 제한 변경으로 인해 암시된 새로 고침으로 설정되는 힙 하드 제한이 이미 커밋된 것보다 낮은 경우 발생할 수 있습니다.

모바일 앱의 세계화

iOS, tvOS 및 MacCatalyst의 모바일 앱은 더 가벼운 ICU 번들을 사용하는 새로운 하이브리드 세계화 모드를 옵트인할 수 있습니다. 하이브리드 모드에서는 세계화 데이터가 부분적으로 ICU 번들에서 가져오고 부분적으로 네이티브 API 호출에서 가져옵니다. 하이브리드 모드는 모바일에서 지원하는 모든 언어를 제공합니다.

하이브리드 모드는 고정 세계화 모드에서 작동할 수 없고 모바일의 ICU 데이터에서 잘라낸 문화권을 사용하는 앱에 가장 적합합니다. 더 작은 ICU 데이터 파일을 로드하려는 경우에도 사용할 수 있습니다. (icudt_hybrid.dat 파일은 기본 ICU 데이터 파일인 icudt.dat보다 34.5% 작습니다.)

하이브리드 세계화 모드를 사용하려면 HybridGlobalization MSBuild 속성을 true로 설정합니다.

<PropertyGroup>
  <HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>

알아야 할 몇 가지 제한 사항이 있습니다.

  • 네이티브 API의 제한 사항으로 인해 일부 세계화 API는 하이브리드 모드에서 지원되지 않습니다.
  • 지원되는 API 중 일부는 동작이 다릅니다.

사용자의 애플리케이션이 영향을 받는지 확인하려면 동작 차이를 참조하세요.

원본 생성 COM interop

.NET 8에는 COM 인터페이스와의 상호 운용을 지원하는 새로운 원본 생성기가 포함되어 있습니다. GeneratedComInterfaceAttribute을(를) 사용하여 인터페이스를 원본 생성기의 COM 인터페이스로 표시할 수 있습니다. 그런 다음 원본 생성기는 C# 코드에서 비관리 코드로 호출할 수 있는 코드를 생성합니다. 또한 비관리 코드에서 C#으로 호출할 수 있는 코드를 생성합니다. 이 원본 생성기는 LibraryImportAttribute와(과) 통합되며 GeneratedComInterfaceAttribute이(가) 포함된 형식을 매개 변수로 사용하고 LibraryImport 특성 메서드에서 반환 형식을 사용할 수 있습니다.

using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
    void DoWork();
}

internal partial class MyNativeLib
{
    [LibraryImport(nameof(MyNativeLib))]
    public static partial void GetComInterface(out IComInterface comInterface);
}

원본 생성기는 또한 GeneratedComInterfaceAttribute 특성이 있는 인터페이스를 구현하는 형식을 비관리 코드에 전달할 수 있도록 새로운 GeneratedComClassAttribute 특성을 지원합니다. 원본 생성기는 인터페이스를 구현하고 호출을 관리되는 구현으로 전달하는 COM 개체를 노출하는 데 필요한 코드를 생성합니다.

GeneratedComInterfaceAttribute 특성이 있는 인터페이스의 메서드는 LibraryImportAttribute와(과) 동일한 형식을 모두 지원하며, 이제 LibraryImportAttribute은(는) GeneratedComInterface 특성 형식과 GeneratedComClass 특성 형식을 지원합니다.

C# 코드가 GeneratedComInterface 특성 인터페이스만 사용하여 비관리 코드에서 COM 개체를 래핑하거나 C#에서 관리 개체를 래핑하여 비관리 코드에 노출하는 경우 Options 속성의 옵션을 사용하여 생성할 코드를 사용자 지정할 수 있습니다. 이러한 옵션은 사용되지 않을 시나리오에 대해 마샬러를 작성할 필요가 없음을 의미합니다.

원본 생성기는 새로운 StrategyBasedComWrappers 형식을 사용하여 COM 개체 래퍼와 관리 개체 래퍼를 만들고 관리합니다. 이 새로운 형식은 COM interop에 대해 예상되는 .NET 사용자 환경을 제공하는 동시에 고급 사용자를 위한 사용자 지정 지점도 제공합니다. 애플리케이션에 COM에서 형식을 정의하기 위한 자체 메커니즘이 있거나 원본 생성 COM이 현재 지원하지 않는 시나리오를 지원해야 하는 경우 시나리오에 누락된 기능을 추가하고 COM 형식에 대해 동일한 .NET 사용자 환경을 얻으려면 새로운 StrategyBasedComWrappers 형식을 사용하는 것이 좋습니다.

Visual Studio를 사용하는 경우 새로운 분석기와 코드 수정을 통해 기존 COM interop 코드를 원본 생성 상호 운용성을 사용하도록 쉽게 변환할 수 있습니다. ComImportAttribute가 있는 각 인터페이스 옆에 전구가 원본 생성 상호 운용성으로 변환하는 옵션을 제공합니다. 수정 사항은 GeneratedComInterfaceAttribute 특성을 사용하도록 인터페이스를 변경합니다. 그리고 GeneratedComInterfaceAttribute를 사용하여 인터페이스를 구현하는 모든 클래스 옆에는 전구가 형식에 GeneratedComClassAttribute 특성을 추가하는 옵션을 제공합니다. 형식이 변환되면 DllImport 메서드를 이동하여 LibraryImportAttribute을(를) 사용할 수 있습니다.

제한 사항

COM 원본 생성기는 new 키워드를 사용하여 COM CoClass와 다음 API를 활성화하는 아파트 선호도를 지원하지 않습니다.

구성 바인딩 소스 생성기

.NET 8에는 ASP.NET Core에서 AOT 및 트림 친화적인 구성을 제공하는 원본 생성기가 도입되었습니다. 생성기는 기존 반사 기반 구현의 대안입니다.

원본 생성기는 형식 정보를 검색하기 위해 Configure(TOptions), BindGet 호출을 조사합니다. 프로젝트에서 생성기가 사용하도록 설정되면 컴파일러는 기존 리플렉션 기반 프레임워크 구현보다 생성된 메서드를 암시적으로 선택합니다.

생성기를 사용하기 위해 소스 코드를 변경할 필요는 없습니다. AOT 웹앱에서는 기본적으로 사용하도록 설정되어 있습니다. 다른 프로젝트 형식의 경우 원본 생성기는 기본적으로 꺼져 있지만 프로젝트 파일에서 EnableConfigurationBindingGenerator 속성을 true(으)로 설정하여 옵트인할 수 있습니다.

<PropertyGroup>
    <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>

다음 코드는 바인더를 호출하는 예를 보여 줍니다.

public class ConfigBindingSG
{
    static void RunIt(params string[] args)
    {
        WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
        IConfigurationSection section = builder.Configuration.GetSection("MyOptions");

        // !! Configure call - to be replaced with source-gen'd implementation
        builder.Services.Configure<MyOptions>(section);

        // !! Get call - to be replaced with source-gen'd implementation
        MyOptions? options0 = section.Get<MyOptions>();

        // !! Bind call - to be replaced with source-gen'd implementation
        MyOptions options1 = new();
        section.Bind(options1);

        WebApplication app = builder.Build();
        app.MapGet("/", () => "Hello World!");
        app.Run();
    }

    public class MyOptions
    {
        public int A { get; set; }
        public string S { get; set; }
        public byte[] Data { get; set; }
        public Dictionary<string, string> Values { get; set; }
        public List<MyClass> Values2 { get; set; }
    }

    public class MyClass
    {
        public int SomethingElse { get; set; }
    }
}

핵심 .NET 라이브러리

이 섹션에는 다음 하위 항목이 포함되어 있습니다.

반영

함수 포인터는 .NET 5에 도입되었지만 당시에는 리플렉션에 대한 해당 지원이 추가되지 않았습니다. typeof 또는 함수 포인터에 대한 리플렉션(예: 각각 typeof(delegate*<void>()) 또는 FieldInfo.FieldType)을 사용할 때 IntPtr이(가) 반환되었습니다. .NET 8부터 대신 System.Type 개체가 반환됩니다. 이 형식은 호출 규칙, 반환 형식 및 매개 변수를 포함한 함수 포인터 메타데이터에 대한 액세스를 제공합니다.

참고 항목

함수에 대한 실제 주소인 함수 포인터 인스턴스는 계속해서 IntPtr(으)로 표시됩니다. 반사 형식만 변경되었습니다.

새로운 기능은 현재 CoreCLR 런타임 및 MetadataLoadContext에서만 구현됩니다.

IsFunctionPointer 등의 System.TypeSystem.Reflection.PropertyInfo, System.Reflection.FieldInfoSystem.Reflection.ParameterInfo에 새 API가 추가되었습니다. 다음 코드는 리플렉션을 위해 새로운 API 중 일부를 사용하는 방법을 보여 줍니다.

using System;
using System.Reflection;

// Sample class that contains a function pointer field.
public unsafe class UClass
{
    public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp;
}

internal class FunctionPointerReflection
{
    public static void RunIt()
    {
        FieldInfo? fieldInfo = typeof(UClass).GetField(nameof(UClass._fp));

        // Obtain the function pointer type from a field.
        Type? fpType = fieldInfo?.FieldType;

        // New methods to determine if a type is a function pointer.
        Console.WriteLine(
        $"IsFunctionPointer: {fpType?.IsFunctionPointer}");
        Console.WriteLine(
            $"IsUnmanagedFunctionPointer: {fpType?.IsUnmanagedFunctionPointer}");

        // New methods to obtain the return and parameter types.
        Console.WriteLine($"Return type: {fpType?.GetFunctionPointerReturnType()}");

        if (fpType is not null)
        {
            foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes())
            {
                Console.WriteLine($"Parameter type: {parameterType}");
            }
        }

        // Access to custom modifiers and calling conventions requires a "modified type".
        Type? modifiedType = fieldInfo?.GetModifiedFieldType();

        // A modified type forwards most members to its underlying type.
        Type? normalType = modifiedType?.UnderlyingSystemType;

        if (modifiedType is not null)
        {
            // New method to obtain the calling conventions.
            foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions())
            {
                Console.WriteLine($"Calling convention: {callConv}");
            }
        }

        // New method to obtain the custom modifiers.
        Type[]? modifiers =
            modifiedType?.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers();

        if (modifiers is not null)
        {
            foreach (Type modreq in modifiers)
            {
                Console.WriteLine($"Required modifier for first parameter: {modreq}");
            }
        }
    }
}

이전 예에서는 다음 출력을 생성합니다.

IsFunctionPointer: True
IsUnmanagedFunctionPointer: True
Return type: System.Void
Parameter type: System.Int32&
Calling convention: System.Runtime.CompilerServices.CallConvSuppressGCTransition
Calling convention: System.Runtime.CompilerServices.CallConvCdecl
Required modifier for first parameter: System.Runtime.InteropServices.InAttribute

직렬화

.NET 8의 System.Text.Json 직렬화 및 역직렬화 기능이 많이 개선되었습니다. 예를 들어, JSON 페이로드에 없는 멤버 처리를 사용자 지정할 수 있습니다.

다음 섹션에서는 기타 직렬화 개선 사항을 설명합니다.

일반적인 JSON 직렬화에 대한 자세한 내용은 .NET의 JSON 직렬화 및 역직렬화를 참조하세요.

추가 형식에 대한 기본 지원

직렬 변환기에는 다음과 같은 추가 형식에 대한 지원이 기본 제공되어 있습니다.

  • Half, Int128UInt128 숫자 형식입니다.

    Console.WriteLine(JsonSerializer.Serialize(
        [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ]
    ));
    // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
    
  • Memory<T>ReadOnlyMemory<T> 값. byte 값은 Base64 문자열로 직렬화되고 기타 형식은 JSON 배열로 직렬화됩니다.

    JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }); // "AQID"
    JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }); // [1,2,3]
    

원본 생성기

.NET 8에는 반사 기반 직렬 변환기와 동등한 네이티브 AOT 환경을 만드는 것을 목표로 하는 System.Text.Json 원본 생성기의 향상된 기능이 포함되어 있습니다. 예시:

  • 이제 원본 생성기는 requiredinit 속성을 사용하여 형식 직렬화를 지원합니다. 둘 다 이미 리플렉션 기반 직렬화에서 지원되었습니다.

  • 원본 생성 코드의 서식이 개선되었습니다.

  • JsonSerializerOptions가 있는 JsonSourceGenerationOptionsAttribute 기능 패리티입니다. 자세한 내용은 옵션 지정(원본 생성)을 참조하세요.

  • 추가 진단(예: SYSLIB1034SYSLIB1039).

  • 무시되거나 액세스할 수 없는 속성 형식을 포함하지 마세요.

  • 임의 형식 종류 내에서 JsonSerializerContext 선언 중첩을 지원합니다.

  • 약한 형식의 원본 생성 시나리오에서 컴파일러 생성 형식 또는 unspeakable 형식을 지원합니다. 컴파일러에서 생성된 형식은 원본 생성기에서 명시적으로 지정할 수 없으므로 이제 System.Text.Json은 런타임 시 가장 가까운 상위 항목 확인을 수행합니다. 이 해결 방법은 값을 직렬화하는 데 가장 적절한 상위 형식을 결정합니다.

  • 새 변환기 형식 JsonStringEnumConverter<TEnum>. 기존 JsonStringEnumConverter 클래스는 네이티브 AOT에서 지원되지 않습니다. 다음과 같이 열거형 형식에 주석을 달 수 있습니다.

    [JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))]
    public enum MyEnum { Value1, Value2, Value3 }
    
    [JsonSerializable(typeof(MyEnum))]
    public partial class MyContext : JsonSerializerContext { }
    

    자세한 내용은 열거형 필드를 문자열로 직렬화를 참조하세요.

  • 새로운 JsonConverter.Type 속성을 사용하면 일반이 아닌 JsonConverter 인스턴스의 형식을 조회할 수 있습니다.

    Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters)
        => converters.Where(converter => converter.Type != null)
                     .ToDictionary(converter => converter.Type!);
    

    이 속성은 JsonConverterFactory 인스턴스에 대해 null을(를) 반환하고 JsonConverter<T> 인스턴스에 대해 typeof(T)을(를) 반환하므로 null을 허용합니다.

원본 생성기 연결

JsonSerializerOptions 클래스에는 기존 TypeInfoResolver 속성을 보완하는 새로운 TypeInfoResolverChain 속성이 포함되어 있습니다. 이러한 속성은 원본 생성기 연결을 위한 계약 사용자 지정에 사용됩니다. 새 속성이 추가된다는 것은 하나의 호출 사이트에서 연결된 모든 구성 요소를 지정할 필요가 없다는 것을 의미합니다. 나중에 추가할 수 있습니다. TypeInfoResolverChain을(를) 사용하면 연결을 검사하거나 연결에서 구성 요소를 제거할 수도 있습니다. 자세한 내용은 원본 생성기 결합을 참조하세요.

또한 JsonSerializerOptions.AddContext<TContext>()은(는) 이제 더 이상 사용되지 않습니다. 이는 TypeInfoResolverTypeInfoResolverChain 속성으로 대체되었습니다. 자세한 내용은 SYSLIB0049를 참조하세요.

인터페이스 계층

.NET 8에는 인터페이스 계층 구조의 속성 직렬화에 대한 지원이 추가되었습니다.

다음 코드는 즉시 구현된 인터페이스와 해당 기본 인터페이스의 속성이 모두 직렬화되는 예를 보여 줍니다.

public static void InterfaceHierarchies()
{
    IDerived value = new DerivedImplement { Base = 0, Derived = 1 };
    string json = JsonSerializer.Serialize(value);
    Console.WriteLine(json); // {"Derived":1,"Base":0}
}

public interface IBase
{
    public int Base { get; set; }
}

public interface IDerived : IBase
{
    public int Derived { get; set; }
}

public class DerivedImplement : IDerived
{
    public int Base { get; set; }
    public int Derived { get; set; }
}

명명 정책

JsonNamingPolicy에는 snake_case(밑줄 포함) 및 kebab-case(하이픈 포함) 속성 이름 변환에 대한 새로운 명명 정책이 포함되어 있습니다. 기존 JsonNamingPolicy.CamelCase 정책과 유사하게 다음 정책을 사용합니다.

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }

자세한 내용은 기본 제공된 명명 정책 사용을 참조하세요.

읽기 전용 속성

이제 읽기 전용 필드 또는 속성(즉, set 접근자가 없는 필드)을 역직렬화할 수 있습니다.

이 지원을 전역적으로 옵트인하려면 새 옵션 PreferredObjectCreationHandling을(를) JsonObjectCreationHandling.Populate(으)로 설정합니다. 호환성이 문제인 경우 속성이 채워질 특정 형식이나 개별 속성에 [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] 특성을 배치하여 기능을 보다 세부적으로 사용하도록 설정할 수도 있습니다.

예를 들어, 두 개의 읽기 전용 속성이 있는 CustomerInfo 형식으로 역직렬화하는 다음 코드를 생각해 보세요.

public static void ReadOnlyProperties()
{
    CustomerInfo customer = JsonSerializer.Deserialize<CustomerInfo>("""
        { "Names":["John Doe"], "Company":{"Name":"Contoso"} }
        """)!;

    Console.WriteLine(JsonSerializer.Serialize(customer));
}

class CompanyInfo
{
    public required string Name { get; set; }
    public string? PhoneNumber { get; set; }
}

[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class CustomerInfo
{
    // Both of these properties are read-only.
    public List<string> Names { get; } = new();
    public CompanyInfo Company { get; } = new()
    {
        Name = "N/A",
        PhoneNumber = "N/A"
    };
}

.NET 8 이전에는 입력 값이 무시되었으며 NamesCompany 속성은 기본값을 보존했습니다.

{"Names":[],"Company":{"Name":"N/A","PhoneNumber":"N/A"}}

이제 입력 값은 역직렬화 중에 읽기 전용 속성을 채우는 데 사용됩니다.

{"Names":["John Doe"],"Company":{"Name":"Contoso","PhoneNumber":"N/A"}}

채우기 역직렬화 동작에 대한 자세한 내용은 초기화된 속성 채우기를 참조하세요.

반사 기반 기본값 사용 안 함

이제 기본적으로 리플렉션 기반 직렬 변환기 사용을 사용하지 않도록 설정할 수 있습니다. 이 사용하지 않도록 설정은 특히 잘린 네이티브 AOT 앱에서 사용하지도 않는 리플렉션 구성 요소가 실수로 루팅되는 것을 방지하는 데 유용합니다. JsonSerializerOptions 인수가 JsonSerializer 직렬화 및 역직렬화 메서드에 전달되도록 요구하여 기본 리플렉션 기반 직렬화를 사용하지 않도록 설정하려면 프로젝트 파일에서 JsonSerializerIsReflectionEnabledByDefault MSBuild 속성을 false(으)로 설정합니다.

새로운 IsReflectionEnabledByDefault API를 사용하여 기능 스위치의 값을 확인합니다. System.Text.Json을(를) 기반으로 빌드하는 라이브러리 빌드자인 경우 실수로 리플렉션 구성 요소를 루팅하지 않고 속성을 사용하여 기본값을 구성할 수 있습니다.

자세한 내용은 반사 기본값 사용 안 함을(를) 참조하세요.

새로운 JsonNode API 메서드

JsonNodeSystem.Text.Json.Nodes.JsonArray 형식에는 다음과 같은 새로운 메서드가 포함됩니다.

public partial class JsonNode
{
    // Creates a deep clone of the current node and all its descendants.
    public JsonNode DeepClone();

    // Returns true if the two nodes are equivalent JSON representations.
    public static bool DeepEquals(JsonNode? node1, JsonNode? node2);

    // Determines the JsonValueKind of the current node.
    public JsonValueKind GetValueKind(JsonSerializerOptions options = null);

    // If node is the value of a property in the parent
    // object, returns its name.
    // Throws InvalidOperationException otherwise.
    public string GetPropertyName();

    // If node is the element of a parent JsonArray,
    // returns its index.
    // Throws InvalidOperationException otherwise.
    public int GetElementIndex();

    // Replaces this instance with a new value,
    // updating the parent object/array accordingly.
    public void ReplaceWith<T>(T value);

    // Asynchronously parses a stream as UTF-8 encoded data
    // representing a single JSON value into a JsonNode.
    public static Task<JsonNode?> ParseAsync(
        Stream utf8Json,
        JsonNodeOptions? nodeOptions = null,
        JsonDocumentOptions documentOptions = default,
        CancellationToken cancellationToken = default);
}

public partial class JsonArray
{
    // Returns an IEnumerable<T> view of the current array.
    public IEnumerable<T> GetValues<T>();
}

Public이 아닌 멤버

JsonIncludeAttributeJsonConstructorAttribute 특성 주석을 사용하여 특정 형식에 대한 직렬화 계약에 public이 아닌 멤버를 선택할 수 있습니다.

public static void NonPublicMembers()
{
    string json = JsonSerializer.Serialize(new MyPoco(42));
    Console.WriteLine(json);
    // {"X":42}

    JsonSerializer.Deserialize<MyPoco>(json);
}

public class MyPoco
{
    [JsonConstructor]
    internal MyPoco(int x) => X = x;

    [JsonInclude]
    internal int X { get; }
}

자세한 내용은 변경이 불가능한 형식, public이 아닌 멤버 및 접근자 사용을 참조하세요.

스트리밍 역직렬화 API

.NET 8에는 새로운 IAsyncEnumerable<T> 스트리밍 역직렬화 확장 메서드(예: GetFromJsonAsAsyncEnumerable)가 포함되어 있습니다. Task<TResult>(예: HttpClientJsonExtensions.GetFromJsonAsync)을(를) 반환하는 유사한 메서드가 존재합니다. 새로운 확장 메서드는 스트리밍 API를 호출하고 IAsyncEnumerable<T>을(를) 반환합니다.

다음 코드는 새로운 확장 메서드를 사용하는 방법을 보여 줍니다.

public async static void StreamingDeserialization()
{
    const string RequestUri = "https://api.contoso.com/books";
    using var client = new HttpClient();
    IAsyncEnumerable<Book?> books = client.GetFromJsonAsAsyncEnumerable<Book>(RequestUri);

    await foreach (Book? book in books)
    {
        Console.WriteLine($"Read book '{book?.title}'");
    }
}

public record Book(int id, string title, string author, int publishedYear);

WithAddedModifier 확장 메서드

새로운 WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) 확장 메서드를 사용하면 임의의 IJsonTypeInfoResolver 인스턴스의 직렬화 계약을 쉽게 수정할 수 있습니다.

var options = new JsonSerializerOptions
{
    TypeInfoResolver = MyContext.Default
        .WithAddedModifier(static typeInfo =>
        {
            foreach (JsonPropertyInfo prop in typeInfo.Properties)
            {
                prop.Name = prop.Name.ToUpperInvariant();
            }
        })
};

새로운 JsonContent.Create 오버로드

이제 트림 안전 또는 원본 만들기 계약을 사용하여 JsonContent 인스턴스를 만들 수 있습니다. 새로운 방법은 다음과 같습니다.

var book = new Book(id: 42, "Title", "Author", publishedYear: 2023);
HttpContent content = JsonContent.Create(book, MyContext.Default.Book);

public record Book(int id, string title, string author, int publishedYear);

[JsonSerializable(typeof(Book))]
public partial class MyContext : JsonSerializerContext
{
}

JsonSerializerOptions 인스턴스 고정

다음 새 메서드를 사용하면 JsonSerializerOptions 인스턴스가 고정되는 시점을 제어할 수 있습니다.

  • JsonSerializerOptions.MakeReadOnly()

    이 오버로드는 트림에 안전하도록 설계되었으므로 옵션 인스턴스가 해결 프로그램으로 구성되지 않은 경우 예외가 발생합니다.

  • JsonSerializerOptions.MakeReadOnly(Boolean)

    이 오버로드에 true을(를) 전달하면 옵션 인스턴스가 누락된 경우 기본 리플렉션 해결 프로그램으로 채워집니다. 이 메서드는 RequiresUnreferenceCode/RequiresDynamicCode로 표시되어 있으므로 네이티브 AOT 애플리케이션에 적합하지 않습니다.

새로운 IsReadOnly 속성을 사용하면 옵션 인스턴스가 고정되었는지 확인할 수 있습니다.

시간 추상화

새로운 TimeProvider 클래스와 ITimer 인터페이스에는 테스트 시나리오에서 시간을 모의할 수 있는 시간 추상화 기능이 추가되었습니다. 또한 시간 추상화를 사용하면 Task.DelayTask.WaitAsync을(를) 사용하여 시간 진행에 의존하는 Task 작업을 모의할 수 있습니다. 시간 추상화는 다음과 같은 필수 시간 작업을 지원합니다.

  • 로컬 및 UTC 시간 검색
  • 성능 측정을 위한 타임스탬프 획득
  • 타이머 만들기

다음 코드 조각은 몇 가지 사용 예를 보여 줍니다.

// Get system time.
DateTimeOffset utcNow = TimeProvider.System.GetUtcNow();
DateTimeOffset localNow = TimeProvider.System.GetLocalNow();

TimerCallback callback = s => ((State)s!).Signal();

// Create a timer using the time provider.
ITimer timer = _timeProvider.CreateTimer(
    callback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);

// Measure a period using the system time provider.
long providerTimestamp1 = TimeProvider.System.GetTimestamp();
long providerTimestamp2 = TimeProvider.System.GetTimestamp();

TimeSpan period = _timeProvider.GetElapsedTime(providerTimestamp1, providerTimestamp2);
// Create a time provider that works with a
// time zone that's different than the local time zone.
private class ZonedTimeProvider(TimeZoneInfo zoneInfo) : TimeProvider()
{
    private readonly TimeZoneInfo _zoneInfo = zoneInfo ?? TimeZoneInfo.Local;

    public override TimeZoneInfo LocalTimeZone => _zoneInfo;

    public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) =>
        new ZonedTimeProvider(zoneInfo);
}

UTF8 개선 사항

형식의 문자열과 같은 표현을 대상 범위에 쓸 수 있도록 하려면 형식에 새로운 IUtf8SpanFormattable 인터페이스를 구현합니다. 이 새 인터페이스는 ISpanFormattable과(와) 밀접하게 관련되어 있지만 UTF16 및 Span<char> 대신 UTF8 및 Span<byte>을(를) 대상으로 합니다.

IUtf8SpanFormattablestring, Span<char> 또는 Span<byte>을(를) 대상 지정하든 정확히 동일한 공유 논리를 사용하여 모든 기본 형식(및 기타 형식)에서 구현되었습니다. 모든 형식(새로운 "B" 이진 파일 지정자 포함)과 모든 문화권을 완벽하게 지원합니다. 즉, 이제 Byte, Complex, Char, DateOnly, DateTime, DateTimeOffset, Decimal, Double, Guid, Half, IPAddress, IPNetwork, Int16, Int32, Int64, Int128, IntPtr, NFloat, SByte, Single, Rune, TimeOnly, TimeSpan, UInt16, UInt32, UInt64, UInt128, UIntPtrVersion에서 UTF8로 직접 형식을 지정할 수 있습니다.

새로운 Utf8.TryWrite 메서드는 UTF16 기반인 기존 MemoryExtensions.TryWrite 메서드에 대응하는 UTF8 기반 메서드를 제공합니다. 보간된 문자열 구문을 사용하여 복잡한 식을 UTF8 바이트 범위로 직접 형식화할 수 있습니다. 예를 들면 다음과 같습니다.

static bool FormatHexVersion(
    short major,
    short minor,
    short build,
    short revision,
    Span<byte> utf8Bytes,
    out int bytesWritten) =>
    Utf8.TryWrite(
        utf8Bytes,
        CultureInfo.InvariantCulture,
        $"{major:X4}.{minor:X4}.{build:X4}.{revision:X4}",
        out bytesWritten);

구현은 형식 값에서 IUtf8SpanFormattable을(를) 인식하고 해당 구현을 사용하여 UTF8 표현을 대상 범위에 직접 작성합니다.

구현에서는 또한 Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) 대응 항목과 함께 대상 범위로의 인코딩 및 디코딩을 지원하는 새로운 Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32) 메서드를 활용합니다. 범위가 결과 상태를 저장할 만큼 길지 않으면 메서드는 예외를 발생시키는 대신 false을(를) 반환합니다.

임의성을 사용하는 방법

System.RandomSystem.Security.Cryptography.RandomNumberGenerator 형식에는 임의성을 사용하기 위한 두 가지 새로운 방법이 도입되었습니다.

GetItems<T>()

새로운 System.Random.GetItemsSystem.Security.Cryptography.RandomNumberGenerator.GetItems 메서드를 사용하면 입력 집합에서 지정된 수의 항목을 임의로 선택할 수 있습니다. 다음 예에서는 Random.Shared 속성이 제공하는 인스턴스에서 System.Random.GetItems<T>()을(를) 사용하여 31개 항목을 배열에 임의로 삽입하는 방법을 보여 줍니다. 이 예는 플레이어가 일련의 색 단추를 기억해야 하는 "Simon" 게임에서 사용될 수 있습니다.

private static ReadOnlySpan<Button> s_allButtons = new[]
{
    Button.Red,
    Button.Green,
    Button.Blue,
    Button.Yellow,
};

// ...

Button[] thisRound = Random.Shared.GetItems(s_allButtons, 31);
// Rest of game goes here ...

Shuffle<T>()

새로운 Random.ShuffleRandomNumberGenerator.Shuffle<T>(Span<T>) 메서드를 사용하면 범위의 순서를 임의로 지정할 수 있습니다. 이러한 방법은 기계 학습에서 학습 바이어스를 줄이는 데 유용합니다(따라서 항상 학습이 첫 번째가 아니며 항상 테스트가 마지막임).

YourType[] trainingData = LoadTrainingData();
Random.Shared.Shuffle(trainingData);

IDataView sourceData = mlContext.Data.LoadFromEnumerable(trainingData);

DataOperationsCatalog.TrainTestData split = mlContext.Data.TrainTestSplit(sourceData);
model = chain.Fit(split.TrainSet);

IDataView predictions = model.Transform(split.TestSet);
// ...

성능 중심 형식

.NET 8에는 앱 성능 개선을 목표로 하는 몇 가지 새로운 형식이 도입되었습니다.

  • System.Collections.Frozen 네임스페이스에는 컬렉션 형식 FrozenDictionary<TKey,TValue>FrozenSet<T>이(가) 포함됩니다. 이러한 형식은 컬렉션이 만들어진 후에는 키와 값을 변경할 수 없습니다. 해당 요구 사항을 사용하면 더 빠른 읽기 작업이 가능합니다(예: TryGetValue()). 이러한 형식은 처음 사용할 때 채워지고 수명이 긴 서비스 기간 동안 지속되는 컬렉션에 특히 유용합니다. 예를 들면 다음과 같습니다.

    private static readonly FrozenDictionary<string, bool> s_configurationData =
        LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true);
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • MemoryExtensions.IndexOfAny와(과) 같은 메서드는 전달된 컬렉션의 모든 값이 처음으로 나타나는 것을 찾습니다. 새로운 System.Buffers.SearchValues<T> 형식은 이러한 메서드에 전달되도록 설계되었습니다. 이에 따라 .NET 8에서는 새 형식의 인스턴스를 허용하는 MemoryExtensions.IndexOfAny와(과) 같은 메서드의 새 오버로드를 추가합니다. SearchValues<T>의 인스턴스를 만들면 후속 검색을 최적화하는 데 필요한 모든 데이터가 그 시점에 파생됩니다. 즉, 작업이 미리 완료됩니다.

  • 새로운 System.Text.CompositeFormat 형식은 컴파일 시간에 알려지지 않은 형식 문자열을 최적화하는 데 유용합니다(예: 형식 문자열이 리소스 파일에서 로드되는 경우). 문자열 구문 분석과 같은 작업을 수행하기 위해 약간의 추가 시간이 소요되지만 사용할 때마다 작업을 수행하지 않아도 됩니다.

    private static readonly CompositeFormat s_rangeMessage =
        CompositeFormat.Parse(LoadRangeMessageResource());
    
    // ...
    static string GetMessage(int min, int max) =>
        string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
    
  • 새로운 System.IO.Hashing.XxHash3System.IO.Hashing.XxHash128 형식은 빠른 XXH3 및 XXH128 해시 알고리즘의 구현을 제공합니다.

System.Numerics 및 System.Runtime.Intrinsics

이 섹션에서는 System.NumericsSystem.Runtime.Intrinsics 네임스페이스의 개선 사항을 다룹니다.

  • Vector256<T>, Matrix3x2Matrix4x4은(는) .NET 8에서 하드웨어 가속을 개선했습니다. 예를 들어, Vector256<T>은(는) 가능한 경우 내부적으로 2x Vector128<T> 작업이 되도록 다시 구현되었습니다. 이는 Arm64와 같이 Vector128.IsHardwareAccelerated == true이지만 Vector256.IsHardwareAccelerated == false인 경우 일부 함수의 부분 가속을 허용합니다.
  • 이제 하드웨어 내장 기능에 ConstExpected 특성으로 주석이 추가됩니다. 이를 통해 사용자는 기본 하드웨어가 상수를 기대하는 시기와 상수가 아닌 값으로 인해 예기치 않게 성능이 저하될 수 있는 시기를 알 수 있습니다.
  • Lerp(TSelf, TSelf, TSelf)Lerp API가 IFloatingPointIeee754<TSelf>에 추가되었으므로 float(Single), double(Double) 및 Half에 추가되었습니다. 이 API를 사용하면 두 값 사이의 선형 보간을 효율적이고 정확하게 수행할 수 있습니다.

Vector512 및 AVX-512

.NET Core 3.0은 x86/x64용 플랫폼별 하드웨어 내장 API를 포함하도록 SIMD 지원을 확장했습니다. .NET 5에는 Arm64에 대한 지원이 추가되었고 .NET 7에는 플랫폼 간 하드웨어 내장 기능이 추가되었습니다. .NET 8은 Vector512<T>을(를) 도입하고 Intel Advanced Vector Extensions 512(AVX-512) 명령을 지원하여 SIMD 지원을 강화합니다.

특히 .NET 8에는 AVX-512의 다음 주요 기능에 대한 지원이 포함되어 있습니다.

  • 512비트 벡터 연산
  • 추가 SIMD 레지스터 16개
  • 128비트, 256비트 및 512비트 벡터에 사용할 수 있는 추가 명령

해당 기능을 지원하는 하드웨어가 있는 경우 Vector512.IsHardwareAccelerated은(는) 이제 true을(를) 보고합니다.

.NET 8은 또한 System.Runtime.Intrinsics.X86 네임스페이스 아래에 여러 플랫폼별 클래스를 추가합니다.

이러한 클래스는 64비트 프로세스에만 사용할 수 있는 명령에 대해 IsSupported 속성과 중첩된 Avx512F.X64 클래스를 노출한다는 점에서 다른 ISA(명령 집합 아키텍처)와 동일한 일반 형태를 따릅니다. 또한 각 클래스에는 해당 명령 집합에 대한 Avx512VL(벡터 길이) 확장을 노출하는 중첩된 Avx512F.VL 클래스가 있습니다.

코드에서 Vector512 관련 또는 Avx512F 관련 지침을 명시적으로 사용하지 않더라도 새로운 AVX-512 지원의 이점을 활용할 수 있습니다. JIT는 Vector128<T> 또는 Vector256<T>을(를) 사용할 때 암시적으로 추가 레지스터 및 명령을 활용할 수 있습니다. 기본 클래스 라이브러리는 Span<T>ReadOnlySpan<T>에 의해 노출된 대부분의 작업과 기본 형식에 대해 노출된 많은 math API에서 내부적으로 이러한 하드웨어 내장 기능을 사용합니다.

데이터 유효성 검사

System.ComponentModel.DataAnnotations 네임스페이스에는 클라우드 네이티브 서비스의 유효성 검사 시나리오를 위한 새로운 데이터 유효성 검사 특성이 포함되어 있습니다. 기존의 DataAnnotations 유효성 검사기는 양식의 필드와 같은 일반적인 UI 데이터 입력 유효성 검사에 맞춰져 있는 반면, 새 특성은 구성 옵션과(와) 같은 비사용자 입력 데이터의 유효성을 검사하도록 설계되었습니다. 새 특성 외에도 RangeAttributeRequiredAttribute 형식에 새 속성이 추가되었습니다.

새 API 설명
RangeAttribute.MinimumIsExclusive
RangeAttribute.MaximumIsExclusive
허용 범위에 경계가 포함되는지 여부를 지정합니다.
System.ComponentModel.DataAnnotations.LengthAttribute 문자열이나 컬렉션의 하한과 상한을 모두 지정합니다. 예를 들어, [Length(10, 20)]에는 컬렉션에 최소 10개 요소, 최대 20개 요소가 필요합니다.
System.ComponentModel.DataAnnotations.Base64StringAttribute 문자열이 유효한 Base64 표현인지 유효성을 검사합니다.
System.ComponentModel.DataAnnotations.AllowedValuesAttribute
System.ComponentModel.DataAnnotations.DeniedValuesAttribute
허용 목록과 거부 목록을 각각 지정합니다. 예: [AllowedValues("apple", "banana", "mango")].

메트릭

새로운 API를 사용하면 MeterInstrument 개체를 만들 때 키-값 쌍 태그를 해당 개체에 연결할 수 있습니다. 게시된 메트릭 집계는 태그를 사용하여 집계된 값을 구별할 수 있습니다.

var options = new MeterOptions("name")
{
    Version = "version",
    // Attach these tags to the created meter.
    Tags = new TagList()
    {
        { "MeterKey1", "MeterValue1" },
        { "MeterKey2", "MeterValue2" }
    }
};

Meter meter = meterFactory!.Create(options);

Counter<int> counterInstrument = meter.CreateCounter<int>(
    "counter", null, null, new TagList() { { "counterKey1", "counterValue1" } }
);
counterInstrument.Add(1);

새 API의 구성 내용:

암호화

.NET 8에는 SHA-3 해싱 기본 형식에 대한 지원이 추가되었습니다. (SHA-3은 현재 OpenSSL 1.1.1 이상 및 Windows 11 Build 25324 이상이 설치된 Linux에서 지원됩니다.) SHA-2를 사용할 수 있는 API는 이제 SHA-3 보완을 제공합니다. 여기에는 해싱으로 SHA3_256, SHA3_384SHA3_512이(가) 포함되고 HMAC로 HMACSHA3_256, HMACSHA3_384HMACSHA3_512이(가) 포함됩니다. 그리고 알고리즘을 구성할 수 있는 해싱으로 HashAlgorithmName.SHA3_256, HashAlgorithmName.SHA3_384HashAlgorithmName.SHA3_512가 포함되고 RSA OAEP 암호화로 RSAEncryptionPadding.OaepSHA3_256, RSAEncryptionPadding.OaepSHA3_384RSAEncryptionPadding.OaepSHA3_512이(가) 포함됩니다.

다음 예에서는 플랫폼이 SHA-3을 지원하는지 확인하기 위해 SHA3_256.IsSupported 속성을 포함한 API를 사용하는 방법을 보여 줍니다.

// Hashing example
if (SHA3_256.IsSupported)
{
    byte[] hash = SHA3_256.HashData(dataToHash);
}
else
{
    // ...
}

// Signing example
if (SHA3_256.IsSupported)
{
     using ECDsa ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
     byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256);
}
else
{
    // ...
}

SHA-3 지원은 현재 암호화 기본 요소를 지원하는 것을 목표로 합니다. 더 높은 수준의 구문과 프로토콜은 처음에는 SHA-3을 완전히 지원할 것으로 예상되지 않습니다. 이러한 프로토콜에는 X.509 인증서, SignedXml 및 COSE가 포함됩니다.

네트워킹

HTTPS 프록시 지원

지금까지 HttpClient이(가) 지원하는 프록시 형식은 모두 "중간자"가 클라이언트가 연결 중인 사이트를 확인할 수 있도록 허용했습니다(HTTPS URI의 경우에도 마찬가지). 이제 HttpClient은(는) 클라이언트와 프록시 사이에 암호화된 채널을 만들어 모든 요청을 완전히 개인 정보 보호하에 처리할 수 있는 HTTPS 프록시를 지원합니다.

HTTPS 프록시를 사용하도록 설정하려면 all_proxy 환경 변수를 설정하거나 WebProxy 클래스를 사용하여 프로그래밍 방식으로 프록시를 제어합니다.

Unix: export all_proxy=https://x.x.x.x:3218 Windows: set all_proxy=https://x.x.x.x:3218

WebProxy 클래스를 사용하여 프로그래밍 방식으로 프록시를 제어할 수도 있습니다.

스트림 기반 ZipFile 방법

.NET 8에는 디렉터리에 포함된 모든 파일을 수집하고 압축한 다음 결과 Zip 파일을 제공된 스트림에 저장할 수 있는 ZipFile.CreateFromDirectory의 새로운 오버로드가 포함되어 있습니다. 마찬가지로, 새로운 ZipFile.ExtractToDirectory 오버로드를 사용하면 압축 파일이 포함된 스트림을 제공하고 해당 콘텐츠를 파일 시스템으로 추출할 수 있습니다. 새로운 오버로드는 다음과 같습니다.

namespace System.IO.Compression;

public static partial class ZipFile
{
    public static void CreateFromDirectory(
        string sourceDirectoryName, Stream destination);

    public static void CreateFromDirectory(
        string sourceDirectoryName,
        Stream destination,
        CompressionLevel compressionLevel,
        bool includeBaseDirectory);

    public static void CreateFromDirectory(
        string sourceDirectoryName,
        Stream destination,
        CompressionLevel compressionLevel,
        bool includeBaseDirectory,
    Encoding? entryNameEncoding);

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, bool overwriteFiles) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, Encoding? entryNameEncoding) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles) { }
}

이러한 새로운 API는 디스크를 중간 단계로 사용할 필요가 없으므로 디스크 공간이 제한된 경우 유용할 수 있습니다.

확장 라이브러리

이 섹션에는 다음 하위 항목이 포함되어 있습니다.

키 지정 DI 서비스

키 지정 DI(종속성 주입) 서비스는 키를 사용하여 DI 서비스를 등록하고 검색하는 수단을 제공합니다. 키를 사용하면 서비스 등록 및 사용 방법의 범위를 지정할 수 있습니다. 다음은 새로운 API 중 일부입니다.

다음 예에서는 키 지정 DI 서비스를 사용하는 방법을 보여 줍니다.

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.AddSingleton<SmallCacheConsumer>();
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
WebApplication app = builder.Build();
app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());
app.MapGet("/big-cache", ([FromKeyedServices("big")] ICache cache) => cache.Get("data"));
app.MapGet("/small-cache", (HttpContext httpContext) => httpContext.RequestServices.GetRequiredKeyedService<ICache>("small").Get("data"));
app.Run();

class BigCacheConsumer([FromKeyedServices("big")] ICache cache)
{
    public object? GetData() => cache.Get("data");
}

class SmallCacheConsumer(IServiceProvider serviceProvider)
{
    public object? GetData() => serviceProvider.GetRequiredKeyedService<ICache>("small").Get("data");
}

public interface ICache
{
    object Get(string key);
}

public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

자세한 내용은 dotnet/runtime#64427를 참조하세요.

호스트된 수명 주기 서비스

이제 호스티드 서비스에는 애플리케이션 수명 주기 동안 실행을 위한 더 많은 옵션이 있습니다. IHostedService(은)는 StartAsyncStopAsync을(를) 제공했으며 이제 IHostedLifecycleService(은)는 다음과 같은 추가 메서드를 제공합니다.

이러한 메서드는 각각 기존 지점 이전과 이후에 실행됩니다.

다음 예에서는 새 API를 사용하는 방법을 보여 줍니다.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

internal class HostedLifecycleServices
{
    public async static void RunIt()
    {
        IHostBuilder hostBuilder = new HostBuilder();
        hostBuilder.ConfigureServices(services =>
        {
            services.AddHostedService<MyService>();
        });

        using (IHost host = hostBuilder.Build())
        {
            await host.StartAsync();
        }
    }

    public class MyService : IHostedLifecycleService
    {
        public Task StartingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StartAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StartedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StopAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StoppedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StoppingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
    }
}

자세한 내용은 dotnet/runtime#86511를 참조하세요.

옵션 유효성 검사

원본 생성기

시작 오버헤드를 줄이고 유효성 검사 기능 집합을 개선하기 위해 유효성 검사 논리를 구현하는 원본 코드 생성기를 도입했습니다. 다음 코드는 모델 예와 유효성 검사기 클래스를 보여 줍니다.

public class FirstModelNoNamespace
{
    [Required]
    [MinLength(5)]
    public string P1 { get; set; } = string.Empty;

    [Microsoft.Extensions.Options.ValidateObjectMembers(
        typeof(SecondValidatorNoNamespace))]
    public SecondModelNoNamespace? P2 { get; set; }
}

public class SecondModelNoNamespace
{
    [Required]
    [MinLength(5)]
    public string P4 { get; set; } = string.Empty;
}

[OptionsValidator]
public partial class FirstValidatorNoNamespace
    : IValidateOptions<FirstModelNoNamespace>
{
}

[OptionsValidator]
public partial class SecondValidatorNoNamespace
    : IValidateOptions<SecondModelNoNamespace>
{
}

앱이 종속성 주입을 사용하는 경우 다음 코드 예에 표시된 대로 유효성 검사를 삽입할 수 있습니다.

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<FirstModelNoNamespace>(
    builder.Configuration.GetSection("some string"));

builder.Services.AddSingleton<
    IValidateOptions<FirstModelNoNamespace>, FirstValidatorNoNamespace>();
builder.Services.AddSingleton<
    IValidateOptions<SecondModelNoNamespace>, SecondValidatorNoNamespace>();

ValidateOptions ResultBuilder 형식

.NET 8에서는 ValidateOptionsResult 개체 만들기를 용이하게 하기 위해 ValidateOptionsResultBuilder 형식을 도입했습니다. 중요한 점은 이 작성기가 여러 오류의 누적을 허용한다는 것입니다. 이전에는 IValidateOptions<TOptions>.Validate(String, TOptions)을(를) 구현하는 데 필요한 ValidateOptionsResult 개체를 만드는 것이 어려웠고 때로는 계층화된 유효성 검사 오류가 발생했습니다. 여러 오류가 발생한 경우 첫 번째 오류에서 유효성 검사 프로세스가 중지되는 경우가 많습니다.

다음 코드 조각은 ValidateOptionsResultBuilder의 사용 예를 보여 줍니다.

ValidateOptionsResultBuilder builder = new();
builder.AddError("Error: invalid operation code");
builder.AddResult(ValidateOptionsResult.Fail("Invalid request parameters"));
builder.AddError("Malformed link", "Url");

// Build ValidateOptionsResult object has accumulating multiple errors.
ValidateOptionsResult result = builder.Build();

// Reset the builder to allow using it in new validation operation.
builder.Clear();

LoggerMessageAttribute 생성자

이제 LoggerMessageAttribute은(는) 추가 생성자 오버로드를 제공합니다. 이전에는 매개 변수가 없는 생성자 또는 모든 매개 변수(이벤트 ID, 로그 수준 및 메시지)가 필요한 생성자를 선택해야 했습니다. 새로운 오버로드는 줄어든 코드로 필수 매개 변수를 지정하는 데 더 큰 유연성을 제공합니다. 이벤트 ID를 제공하지 않으면 시스템이 자동으로 생성합니다.

public LoggerMessageAttribute(LogLevel level, string message);
public LoggerMessageAttribute(LogLevel level);
public LoggerMessageAttribute(string message);

확장 메트릭

IMeterFactory 인터페이스

DI(종속성 주입) 컨테이너에 새 IMeterFactory 인터페이스를 등록하고 이를 사용하여 격리된 방식으로 Meter 개체를 만들 수 있습니다.

기본 미터 팩터리 구현을 사용하여 DI 컨테이너에 IMeterFactory을(를) 등록합니다.

// 'services' is the DI IServiceCollection.
services.AddMetrics();

그런 다음 소비자는 미터 팩터리를 확보하고 이를 사용하여 새 Meter 개체를 만들 수 있습니다.

IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();

MeterOptions options = new MeterOptions("MeterName")
{
    Version = "version",
};

Meter meter = meterFactory.Create(options);

MetricCollector<T> 클래스

새로운 MetricCollector<T> 클래스를 사용하면 타임스탬프와 함께 메트릭 측정값을 기록할 수 있습니다. 또한 이 클래스는 정확한 타임스탬프 생성을 위해 선택한 시간 공급자를 사용할 수 있는 유연성을 제공합니다.

const string CounterName = "MyCounter";
DateTimeOffset now = DateTimeOffset.Now;

var timeProvider = new FakeTimeProvider(now);
using var meter = new Meter(Guid.NewGuid().ToString());
Counter<long> counter = meter.CreateCounter<long>(CounterName);
using var collector = new MetricCollector<long>(counter, timeProvider);

Assert.IsNull(collector.LastMeasurement);

counter.Add(3);

// Verify the update was recorded.
Assert.AreEqual(counter, collector.Instrument);
Assert.IsNotNull(collector.LastMeasurement);

Assert.AreSame(collector.GetMeasurementSnapshot().Last(), collector.LastMeasurement);
Assert.AreEqual(3, collector.LastMeasurement.Value);
Assert.AreEqual(now, collector.LastMeasurement.Timestamp);

System.Numerics.Tensors.TensorPrimitives

업데이트된 System.Numerics.Tensors NuGet 패키지에는 텐서 작업에 대한 지원을 추가하는 새로운 TensorPrimitives 네임스페이스의 API가 포함되어 있습니다. 텐서 기본 형식은 AI 및 기계 학습과 같은 데이터 집약적인 워크로드를 최적화합니다.

의미 체계 검색 및 RAG(검색 증강 생성)와 같은 AI 워크로드는 관련 데이터로 프롬프트를 보강하여 ChatGPT와 같은 대규모 언어 모델의 자연어 기능을 확장합니다. 이러한 워크로드의 경우 질문에 답하기 위해 가장 관련성이 높은 데이터를 찾기 위한 코사인 유사성과 같은 벡터 작업이 중요합니다. System.Numerics.Tensors.TensorPrimitives 패키지는 벡터 작업을 위한 API를 제공합니다. 즉, 외부 의존 관계를 사용하거나 자체 구현을 작성할 필요가 없습니다.

이 패키지는 System.Numerics.Tensors 패키지를 바꿉니다.

자세한 내용은 .NET 8 RC 2 발표 블로그 게시물을 참조하세요.

참고 항목