.NET 정규식 소스 생성기
정규식은 개발자가 검색되는 패턴을 표현할 수 있는 문자열로, 검색된 문자열에서 텍스트를 검색하고 결과를 하위 집합으로 추출하는 일반적인 방법입니다. .NET에서 System.Text.RegularExpressions
네임스페이스는 Regex 인스턴스 및 정적 메서드를 정의하고 사용자 정의 패턴과 일치시키는 데 사용됩니다. 이 문서에서는 소스 생성을 사용하여 Regex
인스턴스를 생성해 성능을 최적화하는 방법을 알아봅니다.
참고 항목
가능하다면 RegexOptions.Compiled 옵션을 사용하여 정규식을 컴파일하는 대신 소스에서 생성된 정규식을 사용하세요. 원본 생성을 통해 앱을 더 빠르게 시작하고, 더 빠르게 실행하고, 더 간편하게 다듬을 수 있습니다. 원본 생성이 가능한 시기를 알아보려면 사용 시기를 참조하세요.
컴파일된 정규식
new Regex("somepattern")
를 작성하면 몇 가지 프로세스가 발생합니다. 지정된 패턴이 구문 분석됩니다. 이는 패턴의 유효성을 보장하고 구문 분석된 정규식을 나타내는 내부 트리로 변환하기 위한 것입니다. 그런 다음 트리가 다양한 방법으로 최적화되어 패턴을 보다 효율적으로 실행할 수 있는 기능적으로 동등한 변형으로 변환합니다. 트리는 정규식 인터프리터 엔진에 일치 방법에 대한 명령을 제공하는 일련의 opcode 및 피연산자로 해석될 수 있는 양식으로 작성됩니다. 일치가 수행되면 인터프리터는 입력 텍스트에 대해 이러한 명령을 처리합니다. 인터프리터는 새 Regex
인스턴스를 인스턴스화하거나 Regex
에서 정적 메서드 중 하나를 호출할 때 사용하는 기본 엔진입니다.
RegexOptions.Compiled를 지정하면 동일한 구문 시간 작업이 모두 수행됩니다. 결과 명령은 반사 방출 기반 컴파일러에 의해 몇 가지 DynamicMethod 개체에 기록되는 IL 명령으로 추가로 변환됩니다. 일치가 수행되면 해당 DynamicMethod
메서드가 호출됩니다. 이 IL은 처리되는 정확한 패턴에 특화된 것을 제외하고 기본적으로 인터프리터가 수행하는 작업을 정확하게 수행합니다. 예를 들어, 패턴에 [ac]
가 포함된 경우 인터프리터는 "이 집합 설명에 지정된 집합과 현재 위치의 입력 문자를 일치시킵니다"라는 opcode를 보게 됩니다. 반면 컴파일된 IL에는 "현재 위치의 입력 문자를 'a'
또는 'c'
와 일치시킵니다"라고 효과적으로 말하는 코드가 포함됩니다. 이 특수 대/소문자 구분 및 패턴 지식을 기반으로 최적화를 수행하는 기능은 RegexOptions.Compiled
지정이 인터프리터보다 훨씬 빠르게 일치를 처리하는 주된 이유 중 일부입니다.
RegexOptions.Compiled
에는 몇 가지 단점이 있습니다. 가장 큰 영향은 구성하는 데 비용이 많이 든다는 것입니다. 인터프리터와 동일한 비용은 물론이고 결과 RegexNode
트리 및 생성된 opcode/피연산자를 IL로 컴파일하기 위한 적지 않은 비용이 추가됩니다. 생성된 IL은 처음 사용할 때 JIT를 추가로 컴파일해야 하므로 시작 시 더 많은 비용이 발생합니다. RegexOptions.Compiled
는 처음 사용할 때의 오버헤드와 이후의 모든 사용에 대한 오버헤드 간의 근본적인 절충을 나타냅니다. System.Reflection.Emit의 사용은 또한 특정 환경에서 RegexOptions.Compiled
의 사용을 억제합니다. 일부 운영 체제는 동적으로 생성된 코드를 실행할 수 없으므로 이러한 시스템에서는 Compiled
가 작동하지 않습니다.
원본 생성
.NET 7에는 새 RegexGenerator
소스 생성기가 도입되었습니다. 원본 생성기는 컴파일러에 연결되고 추가 소스 코드로 컴파일 단위를 확장하는 구성 요소입니다. .NET SDK(버전 7 이상)에는 Regex
를 반환하는 부분 메서드에서 GeneratedRegexAttribute 특성을 인식하는 원본 생성기가 포함되어 있습니다. 원본 생성기는 Regex
에 대한 모든 논리를 포함하는 해당 메서드의 구현을 제공합니다. 예를 들어, 이전에 다음과 같은 코드를 작성했을 수 있습니다.
private static readonly Regex s_abcOrDefGeneratedRegex =
new(pattern: "abc|def",
options: RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static void EvaluateText(string text)
{
if (s_abcOrDefGeneratedRegex.IsMatch(text))
{
// Take action with matching text
}
}
원본 생성기를 사용하려면 이전 코드를 다음과 같이 다시 작성합니다.
[GeneratedRegex("abc|def", RegexOptions.IgnoreCase, "en-US")]
private static partial Regex AbcOrDefGeneratedRegex();
private static void EvaluateText(string text)
{
if (AbcOrDefGeneratedRegex().IsMatch(text))
{
// Take action with matching text
}
}
팁
RegexOptions.Compiled
플래그는 원본 생성기에서 무시되므로 원본 생성 버전에서는 필요하지 않습니다.
생성된 AbcOrDefGeneratedRegex()
구현은 마찬가지로 싱글톤 Regex
인스턴스를 캐시하므로 코드를 사용하는 데 추가 캐싱이 필요하지 않습니다.
다음 이미지는 소스 생성 캐시 인스턴스, internal
에서 소스 생성기가 방출하는 Regex
하위 클래스의 화면 캡처입니다.
그러나 보다시피 new Regex(...)
만 수행하는 것이 아닙니다. 대신 소스 생성기는 IL에서 RegexOptions.Compiled
가 내보내는 것과 유사한 논리를 사용하여 C# 코드로 사용자 지정 Regex
파생 구현을 내보냅니다. RegexOptions.Compiled
의 모든 처리량 성능 이점(실제로는 그 이상)과 Regex.CompileToAssembly
의 시작 이점을 얻으면서도 CompileToAssembly
의 복잡성은 없습니다. 내보내는 원본은 프로젝트의 일부이므로 쉽게 보고 디버그할 수도 있습니다.
팁
Visual Studio에서 부분 메서드 선언을 마우스 오른쪽 단추로 클릭하고 정의로 이동을 선택합니다. 또는 솔루션 탐색기에서 프로젝트 노드를 선택한 다음 종속성>분석기>System.Text.RegularExpressions.Generator>System.Text.RegularExpressions.Generator.RegexGenerator>RegexGenerator.g.cs를 확장하여 이 정규식 생성기에서 생성된 C# 코드를 확인합니다.
중단점을 설정하고, 한 단계씩 실행할 수 있으며, 학습 도구로 사용하여 정규식 엔진이 입력에서 패턴을 처리하는 방법을 정확하게 이해할 수 있습니다. 생성기는 식의 내용과 사용되는 위치를 한눈에 파악할 수 있도록 XML(트리플 슬래시) 주석을 생성합니다.
소스 생성 파일 내부
.NET 7에서는 소스 생성기 및 RegexCompiler
가 모두 거의 완전히 다시 작성되어 생성되는 코드의 구조가 근본적으로 달라졌습니다. 이 접근 방식은 모든 구문을 처리하도록 확장되었으며(한 가지 주의 사항), 새 접근 방식에 따라 여전히 RegexCompiler
와 소스 생성기가 대부분 1:1로 매핑됩니다. abc|def
식의 주 함수 중 하나에 대한 소스 생성기 출력을 예로 들어보겠습니다.
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
int pos = base.runtextpos;
int matchStart = pos;
ReadOnlySpan<char> slice = inputSpan.Slice(pos);
// Match with 2 alternative expressions, atomically.
{
if (slice.IsEmpty)
{
return false; // The input didn't match.
}
switch (slice[0])
{
case 'A' or 'a':
if ((uint)slice.Length < 3 ||
!slice.Slice(1).StartsWith("bc", StringComparison.OrdinalIgnoreCase)) // Match the string "bc" (ordinal case-insensitive)
{
return false; // The input didn't match.
}
pos += 3;
slice = inputSpan.Slice(pos);
break;
case 'D' or 'd':
if ((uint)slice.Length < 3 ||
!slice.Slice(1).StartsWith("ef", StringComparison.OrdinalIgnoreCase)) // Match the string "ef" (ordinal case-insensitive)
{
return false; // The input didn't match.
}
pos += 3;
slice = inputSpan.Slice(pos);
break;
default:
return false; // The input didn't match.
}
}
// The input matched.
base.runtextpos = pos;
base.Capture(0, matchStart, pos);
return true;
}
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
int pos = base.runtextpos;
int matchStart = pos;
int capture_starting_pos = 0;
ReadOnlySpan<char> slice = inputSpan.Slice(pos);
// 1st capture group.
//{
capture_starting_pos = pos;
// Match with 2 alternative expressions.
//{
if (slice.IsEmpty)
{
UncaptureUntil(0);
return false; // The input didn't match.
}
switch (slice[0])
{
case 'a':
pos++;
slice = inputSpan.Slice(pos);
break;
case 'b':
// Match 'c'.
if ((uint)slice.Length < 2 || slice[1] != 'c')
{
UncaptureUntil(0);
return false; // The input didn't match.
}
pos += 2;
slice = inputSpan.Slice(pos);
break;
default:
UncaptureUntil(0);
return false; // The input didn't match.
}
//}
base.Capture(1, capture_starting_pos, pos);
//}
// Match 'd'.
if (slice.IsEmpty || slice[0] != 'd')
{
UncaptureUntil(0);
return false; // The input didn't match.
}
// The input matched.
pos++;
base.runtextpos = pos;
base.Capture(0, matchStart, pos);
return true;
// <summary>Undo captures until it reaches the specified capture position.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void UncaptureUntil(int capturePosition)
{
while (base.Crawlpos() > capturePosition)
{
base.Uncapture();
}
}
}
소스 생성 코드의 목표는 따라가기 쉬운 구조, 각 단계에서 수행되는 작업을 설명하는 주석, 사람이 작성한 것과 같은 코드 등 이해하기 용이해야 한다는 것입니다. 역추적이 관련된 경우에도 역추적 구조는 스택을 사용하여 다음에 이동할 위치를 나타내는 대신 코드 구조의 일부가 됩니다. 예를 들어 식이 [ab]*[bc]
일 때 생성된 것과 동일한 일치 함수의 코드는 다음과 같습니다.
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
int pos = base.runtextpos;
int matchStart = pos;
int charloop_starting_pos = 0, charloop_ending_pos = 0;
ReadOnlySpan<char> slice = inputSpan.Slice(pos);
// Match a character in the set [ABab] greedily any number of times.
//{
charloop_starting_pos = pos;
int iteration = slice.IndexOfAnyExcept(Utilities.s_ascii_600000006000000);
if (iteration < 0)
{
iteration = slice.Length;
}
slice = slice.Slice(iteration);
pos += iteration;
charloop_ending_pos = pos;
goto CharLoopEnd;
CharLoopBacktrack:
if (Utilities.s_hasTimeout)
{
base.CheckTimeout();
}
if (charloop_starting_pos >= charloop_ending_pos ||
(charloop_ending_pos = inputSpan.Slice(charloop_starting_pos, charloop_ending_pos - charloop_starting_pos).LastIndexOfAny(Utilities.s_ascii_C0000000C000000)) < 0)
{
return false; // The input didn't match.
}
charloop_ending_pos += charloop_starting_pos;
pos = charloop_ending_pos;
slice = inputSpan.Slice(pos);
CharLoopEnd:
//}
// Advance the next matching position.
if (base.runtextpos < pos)
{
base.runtextpos = pos;
}
// Match a character in the set [BCbc].
if (slice.IsEmpty || ((uint)((slice[0] | 0x20) - 'b') > (uint)('c' - 'b')))
{
goto CharLoopBacktrack;
}
// The input matched.
pos++;
base.runtextpos = pos;
base.Capture(0, matchStart, pos);
return true;
}
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
int pos = base.runtextpos;
int matchStart = pos;
int charloop_starting_pos = 0, charloop_ending_pos = 0;
ReadOnlySpan<char> slice = inputSpan.Slice(pos);
// Match a character in the set [ABab] greedily any number of times.
//{
charloop_starting_pos = pos;
int iteration = slice.IndexOfAnyExcept("ABab");
if (iteration < 0)
{
iteration = slice.Length;
}
slice = slice.Slice(iteration);
pos += iteration;
charloop_ending_pos = pos;
goto CharLoopEnd;
CharLoopBacktrack:
if (Utilities.s_hasTimeout)
{
base.CheckTimeout();
}
if (charloop_starting_pos >= charloop_ending_pos ||
(charloop_ending_pos = inputSpan.Slice(charloop_starting_pos, charloop_ending_pos - charloop_starting_pos).LastIndexOfAny("BCbc")) < 0)
{
return false; // The input didn't match.
}
charloop_ending_pos += charloop_starting_pos;
pos = charloop_ending_pos;
slice = inputSpan.Slice(pos);
CharLoopEnd:
//}
// Advance the next matching position.
if (base.runtextpos < pos)
{
base.runtextpos = pos;
}
// Match a character in the set [BCbc].
if (slice.IsEmpty || ((uint)((slice[0] | 0x20) - 'b') > (uint)('c' - 'b')))
{
goto CharLoopBacktrack;
}
// The input matched.
pos++;
base.runtextpos = pos;
base.Capture(0, matchStart, pos);
return true;
}
역추적 위치로 내보내는 CharLoopBacktrack
레이블과 정규식의 후속 부분이 실패할 때 해당 위치로 이동하는 데 사용되는 goto
를 사용하여 코드에서 역추적 구조를 확인할 수 있습니다.
코드를 구현하는 RegexCompiler
와 소스 생성기를 살펴보면 비슷한 이름의 메서드, 유사한 호출 구조, 구현 전체에서 유사한 주석 등 매우 비슷하게 표시됩니다. 대부분의 경우 IL 및 C#로 작성되었다는 차이를 제외하면 동일한 코드입니다. 물론 C# 컴파일러는 C#을 IL로 변환해야 하므로 두 경우 모두 결과 IL이 동일하지 않을 수 있습니다. 소스 생성기는 C# 컴파일러가 다양한 C# 구문을 추가로 최적화한다는 장점 때문에 다양한 경우에 C# 컴파일러를 사용합니다. 소스 생성기가 RegexCompiler
보다 최적화된 일치 코드를 생성하는 몇 가지 구체적인 경우가 있습니다. 예를 들어 이전 예제 중 하나에서 switch 문을 내보내는 소스 생성기가 있습니다. 'a'
에 대한 분기 하나와 'b'
에 대한 또 다른 분기가 있습니다. C# 컴파일러는 switch 문을 최적화하는 데 매우 능숙하여 여러 전략을 사용하여 처리할 수 있으므로 소스 생성기에는 RegexCompiler
에 없는 특수 최적화가 있습니다. 변경의 경우 소스 생성기는 모든 분기를 살펴보고 모든 분기가 다른 시작 문자로 시작된다는 것을 증명할 수 있는 경우 첫 번째 문자에 대한 switch 문을 내보내고 해당 변경에 대한 역추적 코드를 출력하지 않습니다.
여기에 약간 더 복잡한 예가 있습니다. 역추적 엔진에서 보다 쉽게 최적화할 수 있고 소스 생성 코드가 더 간단해지는 방식으로 리팩터링할 수 있는지 여부를 확인하기 위해 변경을 더 집중적으로 분석합니다. 이러한 최적화 중 하나는 분기에서 공통 접두사를 추출하도록 지원하고 변경이 원자성이어서 순서가 중요하지 않은 경우 더 많은 추출을 허용하도록 분기를 다시 정렬합니다. 다음과 같은 일치 함수를 생성하는, 다음 요일 패턴 Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday
대한 영향을 확인할 수 있습니다.
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
int pos = base.runtextpos;
int matchStart = pos;
char ch;
ReadOnlySpan<char> slice = inputSpan.Slice(pos);
// Match with 6 alternative expressions, atomically.
{
int alternation_starting_pos = pos;
// Branch 0
{
if ((uint)slice.Length < 6 ||
!slice.StartsWith("monday", StringComparison.OrdinalIgnoreCase)) // Match the string "monday" (ordinal case-insensitive)
{
goto AlternationBranch;
}
pos += 6;
slice = inputSpan.Slice(pos);
goto AlternationMatch;
AlternationBranch:
pos = alternation_starting_pos;
slice = inputSpan.Slice(pos);
}
// Branch 1
{
if ((uint)slice.Length < 7 ||
!slice.StartsWith("tuesday", StringComparison.OrdinalIgnoreCase)) // Match the string "tuesday" (ordinal case-insensitive)
{
goto AlternationBranch1;
}
pos += 7;
slice = inputSpan.Slice(pos);
goto AlternationMatch;
AlternationBranch1:
pos = alternation_starting_pos;
slice = inputSpan.Slice(pos);
}
// Branch 2
{
if ((uint)slice.Length < 9 ||
!slice.StartsWith("wednesday", StringComparison.OrdinalIgnoreCase)) // Match the string "wednesday" (ordinal case-insensitive)
{
goto AlternationBranch2;
}
pos += 9;
slice = inputSpan.Slice(pos);
goto AlternationMatch;
AlternationBranch2:
pos = alternation_starting_pos;
slice = inputSpan.Slice(pos);
}
// Branch 3
{
if ((uint)slice.Length < 8 ||
!slice.StartsWith("thursday", StringComparison.OrdinalIgnoreCase)) // Match the string "thursday" (ordinal case-insensitive)
{
goto AlternationBranch3;
}
pos += 8;
slice = inputSpan.Slice(pos);
goto AlternationMatch;
AlternationBranch3:
pos = alternation_starting_pos;
slice = inputSpan.Slice(pos);
}
// Branch 4
{
if ((uint)slice.Length < 6 ||
!slice.StartsWith("fr", StringComparison.OrdinalIgnoreCase) || // Match the string "fr" (ordinal case-insensitive)
((((ch = slice[2]) | 0x20) != 'i') & (ch != 'İ')) || // Match a character in the set [Ii\u0130].
!slice.Slice(3).StartsWith("day", StringComparison.OrdinalIgnoreCase)) // Match the string "day" (ordinal case-insensitive)
{
goto AlternationBranch4;
}
pos += 6;
slice = inputSpan.Slice(pos);
goto AlternationMatch;
AlternationBranch4:
pos = alternation_starting_pos;
slice = inputSpan.Slice(pos);
}
// Branch 5
{
// Match a character in the set [Ss].
if (slice.IsEmpty || ((slice[0] | 0x20) != 's'))
{
return false; // The input didn't match.
}
// Match with 2 alternative expressions, atomically.
{
if ((uint)slice.Length < 2)
{
return false; // The input didn't match.
}
switch (slice[1])
{
case 'A' or 'a':
if ((uint)slice.Length < 8 ||
!slice.Slice(2).StartsWith("turday", StringComparison.OrdinalIgnoreCase)) // Match the string "turday" (ordinal case-insensitive)
{
return false; // The input didn't match.
}
pos += 8;
slice = inputSpan.Slice(pos);
break;
case 'U' or 'u':
if ((uint)slice.Length < 6 ||
!slice.Slice(2).StartsWith("nday", StringComparison.OrdinalIgnoreCase)) // Match the string "nday" (ordinal case-insensitive)
{
return false; // The input didn't match.
}
pos += 6;
slice = inputSpan.Slice(pos);
break;
default:
return false; // The input didn't match.
}
}
}
AlternationMatch:;
}
// The input matched.
base.runtextpos = pos;
base.Capture(0, matchStart, pos);
return true;
}
private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
int pos = base.runtextpos;
int matchStart = pos;
ReadOnlySpan<char> slice = inputSpan.Slice(pos);
// Match with 5 alternative expressions, atomically.
{
if (slice.IsEmpty)
{
return false; // The input didn't match.
}
switch (slice[0])
{
case 'M':
// Match the string "onday".
if (!slice.Slice(1).StartsWith("onday"))
{
return false; // The input didn't match.
}
pos += 6;
slice = inputSpan.Slice(pos);
break;
case 'T':
// Match with 2 alternative expressions, atomically.
{
if ((uint)slice.Length < 2)
{
return false; // The input didn't match.
}
switch (slice[1])
{
case 'u':
// Match the string "esday".
if (!slice.Slice(2).StartsWith("esday"))
{
return false; // The input didn't match.
}
pos += 7;
slice = inputSpan.Slice(pos);
break;
case 'h':
// Match the string "ursday".
if (!slice.Slice(2).StartsWith("ursday"))
{
return false; // The input didn't match.
}
pos += 8;
slice = inputSpan.Slice(pos);
break;
default:
return false; // The input didn't match.
}
}
break;
case 'W':
// Match the string "ednesday".
if (!slice.Slice(1).StartsWith("ednesday"))
{
return false; // The input didn't match.
}
pos += 9;
slice = inputSpan.Slice(pos);
break;
case 'F':
// Match the string "riday".
if (!slice.Slice(1).StartsWith("riday"))
{
return false; // The input didn't match.
}
pos += 6;
slice = inputSpan.Slice(pos);
break;
case 'S':
// Match with 2 alternative expressions, atomically.
{
if ((uint)slice.Length < 2)
{
return false; // The input didn't match.
}
switch (slice[1])
{
case 'a':
// Match the string "turday".
if (!slice.Slice(2).StartsWith("turday"))
{
return false; // The input didn't match.
}
pos += 8;
slice = inputSpan.Slice(pos);
break;
case 'u':
// Match the string "nday".
if (!slice.Slice(2).StartsWith("nday"))
{
return false; // The input didn't match.
}
pos += 6;
slice = inputSpan.Slice(pos);
break;
default:
return false; // The input didn't match.
}
}
break;
default:
return false; // The input didn't match.
}
}
// The input matched.
base.runtextpos = pos;
base.Capture(0, matchStart, pos);
return true;
}
Thursday
가 Tuesday
바로 뒤로 다시 정렬되었고, Tuesday
/Thursday
쌍과 Saturday
/Sunday
쌍 모두 여러 수준의 스위치로 구성되었습니다. 극단적인 경우 여러 단어의 긴 변경을 만드는 경우 소스 생성기는 trie^1의 논리적 등가를 내보내고 단어의 나머지 부분을 처리하기 위해 각 문자를 읽고 분기로 switch
ing합니다. 이는 단어를 일치시킬 수 있는 매우 효율적인 방법이며 여기서 소스 생성기가 수행하는 작업입니다.
동시에 소스 생성기에는 IL로 직접 출력할 때는 존재하지 않는 다른 문제가 있습니다. 몇 가지 코드 예제를 다시 보면 일부 중괄호가 다소 이상하게 주석 처리된 것을 볼 수 있습니다. 이것은 실수가 아닙니다. 소스 생성기는 이러한 중괄호가 주석 처리되지 않은 경우 역추적 구조가 해당 범위 외부에서 해당 범위 내에 정의된 레이블로 이동하는 데 의존한다고 인식합니다. 이러한 레이블은 goto
에 표시되지 않으며 코드가 컴파일되지 않습니다. 따라서 소스 생성기는 방해가 되는 범위가 없도록 해야 합니다. 경우에 따라 여기에서와 같이 범위를 주석 처리하기만 하면 됩니다. 이것이 가능하지 않은 다른 경우에는 문제가 될 수 있으면 범위가 필요한 구문(예: 다중 문 if
블록)을 사용하지 않습니다.
소스 생성기는 한 가지 예외를 제외하고 모든 RegexCompiler
핸들을 처리합니다. RegexOptions.IgnoreCase
처리와 마찬가지로 구현은 이제 대/소문자 테이블을 사용하여 생성 시 집합을 생성하고 IgnoreCase
역참조 일치가 해당 대/소문자 테이블을 참조하는 데 필요한 방법을 설명합니다. 해당 테이블은 System.Text.RegularExpressions.dll
내부에 있으며, 적어도 지금은 해당 어셈블리 외부의 코드(소스 생성기에서 내보낸 코드 포함)가 액세스할 수 없습니다. 따라서 IgnoreCase
역참조 처리는 소스 생성기에서 문제가 되며 지원되지 않습니다. 이것이 RegexCompiler
는 지원하지만 소스 생성기는 지원하지 않는 한 구문입니다. 드물지만 이러한 패턴 중 하나를 사용하려고 하면 소스 생성기가 사용자 지정 구현을 내보내는 대신 일반 Regex
인스턴스 캐싱으로 대체됩니다.
또한 소스 생성기와 RegexCompiler
모두 새 RegexOptions.NonBacktracking
을 지원하지 않습니다. RegexOptions.Compiled | RegexOptions.NonBacktracking
을 지정하면 Compiled
플래그는 무시되고, 소스 생성기에 NonBacktracking
을 지정하면 일반 Regex
인스턴스 캐싱으로 대체됩니다.
사용하는 경우
일반적인 참고 자료는 소스 생성기를 사용할 수 있는 경우라면 사용하라는 것입니다. 현재 C#에서 Regex
를 컴파일 시간에 알려진 인수와 함께 사용 중이고, 특히 (정규식이 더 빠른 처리량의 이점을 얻을 수 있는 핫스팟으로 식별되었기 때문에) 이미 RegexOptions.Compiled
를 사용하고 있는 경우 소스 생성기를 사용하는 것이 좋습니다. 소스 생성기는 정규식에 다음과 같은 이점을 제공합니다.
RegexOptions.Compiled
의 모든 처리량 이점.- 런타임에 모든 정규식 구문 분석, 분석 및 컴파일을 수행할 필요가 없다는 시작 이점.
- 정규식에 대해 생성된 코드와 함께 미리 컴파일을 사용하는 옵션.
- 정규식에 대한 향상된 디버그 기능 및 이해.
RegexCompiler
와 연결된 코드를 잘라내어 앱의 크기를 줄일 수 있는 가능성(또한 리플렉션 내보내기 자체에서도 가능).
소스 생성기가 사용자 지정 구현을 생성할 수 없는, RegexOptions.NonBacktracking
과 같은 옵션을 함께 사용하는 경우에도 구현을 설명하는 캐싱 및 XML 주석을 계속 내보내므로 유용합니다. 소스 생성기의 주요 단점은 어셈블리에 추가 코드를 내보내므로 크기가 증가할 가능성이 있다는 것입니다. 앱에 정규식이 더 많을수록 더 많은 코드가 내보내집니다. 어떤 상황에서는 RegexOptions.Compiled
가 불필요할 수 있는 것처럼 원본 생성기도 불필요할 수 있습니다. 예를 들어 거의 필요하지 않고 처리량이 중요하지 않은 정규식이 있는 경우 이 산발적인 사용에 인터프리터만 사용하는 것이 더 유용할 수 있습니다.
추가 정보
.NET