기본 형식: .NET용 확장 라이브러리

이 문서에서는 Microsoft.Extensions.Primitives 라이브러리에 관해 알아봅니다. 이 문서의 기본 형식은 BCL의 .NET 기본 형식 또는 C# 언어의 기본 형식과 혼동되지 ‘않습니다’. 대신, 기본 라이브러리 내의 형식은 다음과 같은 일부 주변 .NET NuGet 패키지의 구성 요소로 사용됩니다.

알림 변경

변경이 발생하는 경우 알림을 전파하는 것은 프로그래밍의 기본 개념입니다. 개체의 관찰된 상태는 자주 변경될 수 있습니다. 변경이 발생하는 경우 Microsoft.Extensions.Primitives.IChangeToken 인터페이스의 구현은 사용하여 관련 대상에 해당 변경을 알리는 데 사용됩니다. 사용 가능한 구현은 다음과 같습니다.

개발자는 고유한 형식을 자유롭게 구현할 수도 있습니다. IChangeToken 인터페이스는 다음과 같은 몇 가지 속성을 정의합니다.

  • IChangeToken.HasChanged: 변경이 발생했는지 나타내는 값을 가져옵니다.
  • IChangeToken.ActiveChangeCallbacks: 토큰이 사전 예방적으로 콜백을 발생시키는지 나타냅니다. false인 경우 토큰 소비자는 HasChanged를 폴링하여 변경 내용을 감지해야 합니다.

인스턴스 기반 기능

CancellationChangeToken의 다음 예제 사용을 참조하세요.

CancellationTokenSource cancellationTokenSource = new();
CancellationChangeToken cancellationChangeToken = new(cancellationTokenSource.Token);

Console.WriteLine($"HasChanged: {cancellationChangeToken.HasChanged}");

static void callback(object? _) =>
    Console.WriteLine("The callback was invoked.");

using (IDisposable subscription =
    cancellationChangeToken.RegisterChangeCallback(callback, null))
{
    cancellationTokenSource.Cancel();
}

Console.WriteLine($"HasChanged: {cancellationChangeToken.HasChanged}\n");

// Outputs:
//     HasChanged: False
//     The callback was invoked.
//     HasChanged: True

앞의 예제에서 CancellationTokenSource가 인스턴스화되고 TokenCancellationChangeToken 생성자에 전달됩니다. HasChanged의 초기 상태는 콘솔에 기록됩니다. 콜백이 콘솔에 호출될 때 기록되는 Action<object?> callback이 생성됩니다. callback이 제공되면 토큰의 RegisterChangeCallback(Action<Object>, Object) 메서드가 호출됩니다. using 문 내에서 cancellationTokenSource가 취소됩니다. 그러면 콜백이 트리거되고 HasChanged의 상태가 다시 콘솔에 기록됩니다.

여러 변경 내용 소스에서 조치를 취해야 하는 경우 CompositeChangeToken을 사용합니다. 이 구현은 하나 이상의 변경 토큰을 집계하고, 변경이 트리거되는 횟수와 관계없이 등록된 각 콜백을 정확히 한 번 발생시킵니다. 다음 예제를 참조하세요.

CancellationTokenSource firstCancellationTokenSource = new();
CancellationChangeToken firstCancellationChangeToken = new(firstCancellationTokenSource.Token);

CancellationTokenSource secondCancellationTokenSource = new();
CancellationChangeToken secondCancellationChangeToken = new(secondCancellationTokenSource.Token);

CancellationTokenSource thirdCancellationTokenSource = new();
CancellationChangeToken thirdCancellationChangeToken = new(thirdCancellationTokenSource.Token);

var compositeChangeToken =
    new CompositeChangeToken(
        new IChangeToken[]
        {
            firstCancellationChangeToken,
            secondCancellationChangeToken,
            thirdCancellationChangeToken
        });

static void callback(object? state) =>
    Console.WriteLine($"The {state} callback was invoked.");

// 1st, 2nd, 3rd, and 4th.
compositeChangeToken.RegisterChangeCallback(callback, "1st");
compositeChangeToken.RegisterChangeCallback(callback, "2nd");
compositeChangeToken.RegisterChangeCallback(callback, "3rd");
compositeChangeToken.RegisterChangeCallback(callback, "4th");

// It doesn't matter which cancellation source triggers the change.
// If more than one trigger the change, each callback is only fired once.
Random random = new();
int index = random.Next(3);
CancellationTokenSource[] sources = new[]
{
    firstCancellationTokenSource,
    secondCancellationTokenSource,
    thirdCancellationTokenSource
};
sources[index].Cancel();

Console.WriteLine();

// Outputs:
//     The 4th callback was invoked.
//     The 3rd callback was invoked.
//     The 2nd callback was invoked.
//     The 1st callback was invoked.

이전 C# 코드에서 세 개의 CancellationTokenSource 개체 인스턴스가 만들어지고 해당 CancellationChangeToken 인스턴스와 쌍을 이룹니다. 복합 토큰은 토큰의 배열을 CompositeChangeToken 생성자에 전달하여 인스턴스화됩니다. Action<object?> callback이 만들어지지만 이번에는 state 개체가 사용되고 형식이 지정된 메시지로 콘솔에 기록됩니다. 콜백은 각각 약간 다른 상태 개체 인수를 사용하여 네 번 등록됩니다. 코드는 의사 난수 생성기를 사용하여 변경 토큰 소스 중 하나를 선택하고(어느 것이든 관계없음) 해당 Cancel() 메서드를 호출합니다. 이렇게 하면 변경이 트리거되어 등록된 각 콜백이 정확히 한 번 호출됩니다.

대체 static 접근 방식

RegisterChangeCallback을 호출하는 대신 Microsoft.Extensions.Primitives.ChangeToken 정적 클래스를 사용할 수 있습니다. 다음 소비 패턴을 고려합니다.

CancellationTokenSource cancellationTokenSource = new();
CancellationChangeToken cancellationChangeToken = new(cancellationTokenSource.Token);

IChangeToken producer()
{
    // The producer factory should always return a new change token.
    // If the token's already fired, get a new token.
    if (cancellationTokenSource.IsCancellationRequested)
    {
        cancellationTokenSource = new();
        cancellationChangeToken = new(cancellationTokenSource.Token);
    }

    return cancellationChangeToken;
}

void consumer() => Console.WriteLine("The callback was invoked.");

using (ChangeToken.OnChange(producer, consumer))
{
    cancellationTokenSource.Cancel();
}

// Outputs:
//     The callback was invoked.

이전 예제와 마찬가지로 changeTokenProducer에서 생성되는 IChangeToken의 구현이 필요합니다. 생산자는 Func<IChangeToken>로 정의되며 호출할 때마다 새 토큰을 반환해야 합니다. consumerstate를 사용하지 않는 경우 Action이고, 제네릭 TState 형식이 변경 알림을 통해 이동하는 경우 Action<TState>입니다.

문자열 토크나이저, 세그먼트 및 값

문자열과 상호 작용하는 것은 애플리케이션 개발에서 일반적입니다. 문자열의 다양한 표현은 구문 분석, 분할 또는 반복됩니다. 기본 형식 라이브러리는 문자열과의 상호 작용을 보다 최적화하고 효율적으로 만드는 데 도움이 되는 몇 가지 선택 형식을 제공합니다. 다음 형식을 고려합니다.

  • StringSegment: 부분 문자열의 최적화된 표현입니다.
  • StringTokenizer: stringStringSegment 인스턴스로 토큰화합니다.
  • StringValues: null, 0, 하나 또는 여러 개의 문자열을 효율적인 방식으로 나타냅니다.

StringSegment 형식

이 섹션에서는 StringSegmentstruct 형식이라고 하는 부분 문자열의 최적화된 표현에 관해 알아봅니다. 일부 StringSegment 속성 및 AsSpan 메서드를 보여 주는 다음 C# 코드 예제를 살펴봅니다.

var segment =
    new StringSegment(
        "This a string, within a single segment representation.",
        14, 25);

Console.WriteLine($"Buffer: \"{segment.Buffer}\"");
Console.WriteLine($"Offset: {segment.Offset}");
Console.WriteLine($"Length: {segment.Length}");
Console.WriteLine($"Value: \"{segment.Value}\"");

Console.Write("Span: \"");
foreach (char @char in segment.AsSpan())
{
    Console.Write(@char);
}
Console.Write("\"\n");

// Outputs:
//     Buffer: "This a string, within a single segment representation."
//     Offset: 14
//     Length: 25
//     Value: " within a single segment "
//     " within a single segment "

앞의 코드는 string 값, offset, length가 제공된 경우 StringSegment를 인스턴스화합니다. StringSegment.Buffer는 원래 문자열 인수이고 StringSegment.ValueStringSegment.OffsetStringSegment.Length 값을 기반으로 하는 부분 문자열입니다.

StringSegment 구조체는 세그먼트와 상호 작용하기 위한 많은 메서드를 제공합니다.

StringTokenizer 형식

StringTokenizer개체는 stringStringSegment 인스턴스로 토큰화하는 구조체 형식입니다. 일반적으로 많은 문자열을 토큰화하려면 문자열을 분할하고 반복해야 합니다. 따라서 String.Split을 고려할 수 있습니다. 이러한 API는 비슷하지만 일반적으로 StringTokenizer가 더 나은 성능을 제공합니다. 먼저 다음 예제를 살펴봅니다.

var tokenizer =
    new StringTokenizer(
        s_nineHundredAutoGeneratedParagraphsOfLoremIpsum,
        new[] { ' ' });

foreach (StringSegment segment in tokenizer)
{
    // Interact with segment
}

이전 코드에서 Lorem Ipsum 텍스트의 자동 생성된 단락 900개와 공백 문자 ' '의 단일 값을 제공하면 StringTokenizer 형식의 인스턴스가 생성됩니다. 토크나이저 내의 각 값은 StringSegment로 표시됩니다. 이 코드는 세그먼트를 반복하여 소비자가 각 segment와 상호 작용할 수 있도록 합니다.

StringTokenizerstring.Split에 비교하는 벤치마크

문자열을 조각화 및 분할하는 다양한 방법을 사용하여 두 가지 방법을 벤치마크와 비교하는 것이 적절합니다. BenchmarkDotNet NuGet 패키지를 사용하여 다음 두 개의 벤치마크 방법을 살펴봅니다.

  1. StringTokenizer 사용:

    StringBuilder buffer = new();
    
    var tokenizer =
        new StringTokenizer(
            s_nineHundredAutoGeneratedParagraphsOfLoremIpsum,
            new[] { ' ', '.' });
    
    foreach (StringSegment segment in tokenizer)
    {
        buffer.Append(segment.Value);
    }
    
  2. String.Split 사용:

    StringBuilder buffer = new();
    
    string[] tokenizer =
        s_nineHundredAutoGeneratedParagraphsOfLoremIpsum.Split(
            new[] { ' ', '.' });
    
    foreach (string segment in tokenizer)
    {
        buffer.Append(segment);
    }
    

두 방법은 모두 API 노출 영역에서 비슷하며 큰 문자열을 청크로 분할할 수 있습니다. 아래 벤치마크 결과는 StringTokenizer 접근 방식이 거의 3배 더 빠르지만 ‘결과가 다를 수 있음’을 보여 줍니다. 모든 성능 고려 사항과 마찬가지로 특정 사용 사례를 평가해야 합니다.

메서드 평균 오류 StdDev 비율
토크나이저 3.315ms 0.0659ms 0.0705ms 0.32
분할 10.257ms 0.2018ms 0.2552ms 1.00

범례

  • 평균: 모든 측정의 산술 평균
  • 오차: 99.9% 신뢰 간격의 절반
  • 표준 편차: 모든 측정의 표준 편차
  • 중앙값: 모든 측정의 상위 절반을 구분하는 값(50번째 백분위 수)
  • 비율: 비율 분포의 평균(현재/기준)
  • 비율 표준 편차: 비율 분포의 표준 편차(현재/기준)
  • 1밀리초: 1밀리초(0.001초)

.NET을 사용한 벤치마킹에 관한 자세한 내용은 BenchmarkDotNet을 참조하세요.

StringValues 형식

StringValues 개체는 효율적인 방식으로 null, 0, 하나 또는 여러 개의 문자열을 나타내는 struct 형식입니다. StringValues 형식은 string? 또는 string?[]? 구문 중 하나를 사용하여 생성할 수 있습니다. 이전 예제의 텍스트를 사용하여 다음 C# 코드를 살펴봅니다.

StringValues values =
    new(s_nineHundredAutoGeneratedParagraphsOfLoremIpsum.Split(
        new[] { '\n' }));

Console.WriteLine($"Count = {values.Count:#,#}");

foreach (string? value in values)
{
    // Interact with the value
}
// Outputs:
//     Count = 1,799

앞의 코드는 문자열 값의 배열이 제공된 경우 StringValues 개체를 인스턴스화합니다. StringValues.Count는 콘솔에 기록됩니다.

StringValues 형식은 다음 컬렉션 형식의 구현입니다.

  • IList<string>
  • ICollection<string>
  • IEnumerable<string>
  • IEnumerable
  • IReadOnlyList<string>
  • IReadOnlyCollection<string>

따라서 반복될 수 있으며 필요에 따라 각 value가 상호 작용할 수 있습니다.

참고 항목