共用方式為


.NET 中的正則表示式最佳做法

.NET 中的正則表達式引擎是一個功能強大且功能完整的工具,可根據模式匹配來處理文字,而不是比較和匹配字面文字。 在大部分情況下,它會快速且有效率地執行模式比對。 不過,在某些情況下,正則表達式引擎看起來可能很慢。 在極端情況下,它甚至可能停止回應,因為它在數小時甚至幾天內處理相對較小的輸入。

本文概述開發人員可採用的一些最佳做法,以確保其正則表達式達到最佳效能。

警告

使用 System.Text.RegularExpressions 來處理不受信任的輸入時,請設定逾時。 惡意使用者可以提供輸入給 RegularExpressions,導致 拒絕服務攻擊。 使用 RegularExpressions 的 ASP.NET Core 框架 API 會傳遞一個逾時設定。

考慮輸入來源

一般而言,正則表達式可以接受兩種類型的輸入:受限制或不受限制。 限制輸入是源自已知或可靠來源且遵循預先定義格式的文字。 不受限制的輸入是源自不可靠來源的文字,例如 Web 使用者,且可能不會遵循預先定義或預期的格式。

正則表達式模式通常會寫入以符合有效的輸入。 也就是說,開發人員會檢查想要比對的文字,然後撰寫符合它的正則表達式模式。 開發人員接著會使用多個有效的輸入項目進行測試,以判斷此模式是否需要更正或進一步詳細說明。 當模式符合所有假設的有效輸入時,它會宣告為生產就緒,而且可以包含在已發行的應用程式中。 此方法會讓正則表示式模式適合比對限制輸入。 不過,它不適合處理不受限制的輸入的比對。

若要比對不受限制的輸入,正則表達式必須有效率地處理三種文字:

  • 符合正則表達式模式的文字。
  • 不符合正則表達式模式的文字。
  • 幾乎符合正則表示式模式的文字。

最後一種文字類型對於設計用來處理限制性輸入的正則表達式來說特別具有挑戰性。 如果該正則表達式也依賴大量 回溯,則正則表達式引擎可能會花費大量時間(在某些情況下,許多小時或數天)處理看似無害的文字。

警告

下列範例使用了一個容易過度回溯的正則表示式,因此可能會拒絕有效的電子郵件位址。 您不應該在電子郵件驗證例程中使用。 如果您要驗證電子郵件地址的正規表示式,請參閱 如何:確認字串是否為有效的電子郵件格式

例如,請考慮常使用但有問題的正則表示式,以驗證電子郵件地址的別名。 正則表達式 ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ 被設計用來處理被視為有效的電子郵件地址。 有效的電子郵件位址包含一個英數字元,後面接著零個或多個字元,可以是英數字元、句點或連字號。 正則表達式的結尾必須是英數位元。 不過,如下列範例所示,雖然這個正則表達式會輕鬆地處理有效的輸入,但其效能在處理幾乎有效的輸入時效率不佳:

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class DesignExample
{
    public static void Main()
    {
        Stopwatch sw;
        string[] addresses = { "AAAAAAAAAAA@contoso.com",
                             "AAAAAAAAAAaaaaaaaaaa!@contoso.com" };
        // The following regular expression should not actually be used to
        // validate an email address.
        string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])*$";
        string input;

        foreach (var address in addresses)
        {
            string mailBox = address.Substring(0, address.IndexOf("@"));
            int index = 0;
            for (int ctr = mailBox.Length - 1; ctr >= 0; ctr--)
            {
                index++;

                input = mailBox.Substring(ctr, index);
                sw = Stopwatch.StartNew();
                Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
                sw.Stop();
                if (m.Success)
                    Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
                                      index, m.Value, sw.Elapsed);
                else
                    Console.WriteLine("{0,2}. Failed  '{1,25}' in {2}",
                                      index, input, sw.Elapsed);
            }
            Console.WriteLine();
        }
    }
}

// The example displays output similar to the following:
//     1. Matched '                        A' in 00:00:00.0007122
//     2. Matched '                       AA' in 00:00:00.0000282
//     3. Matched '                      AAA' in 00:00:00.0000042
//     4. Matched '                     AAAA' in 00:00:00.0000038
//     5. Matched '                    AAAAA' in 00:00:00.0000042
//     6. Matched '                   AAAAAA' in 00:00:00.0000042
//     7. Matched '                  AAAAAAA' in 00:00:00.0000042
//     8. Matched '                 AAAAAAAA' in 00:00:00.0000087
//     9. Matched '                AAAAAAAAA' in 00:00:00.0000045
//    10. Matched '               AAAAAAAAAA' in 00:00:00.0000045
//    11. Matched '              AAAAAAAAAAA' in 00:00:00.0000045
//
//     1. Failed  '                        !' in 00:00:00.0000447
//     2. Failed  '                       a!' in 00:00:00.0000071
//     3. Failed  '                      aa!' in 00:00:00.0000071
//     4. Failed  '                     aaa!' in 00:00:00.0000061
//     5. Failed  '                    aaaa!' in 00:00:00.0000081
//     6. Failed  '                   aaaaa!' in 00:00:00.0000126
//     7. Failed  '                  aaaaaa!' in 00:00:00.0000359
//     8. Failed  '                 aaaaaaa!' in 00:00:00.0000414
//     9. Failed  '                aaaaaaaa!' in 00:00:00.0000758
//    10. Failed  '               aaaaaaaaa!' in 00:00:00.0001462
//    11. Failed  '              aaaaaaaaaa!' in 00:00:00.0002885
//    12. Failed  '             Aaaaaaaaaaa!' in 00:00:00.0005780
//    13. Failed  '            AAaaaaaaaaaa!' in 00:00:00.0011628
//    14. Failed  '           AAAaaaaaaaaaa!' in 00:00:00.0022851
//    15. Failed  '          AAAAaaaaaaaaaa!' in 00:00:00.0045864
//    16. Failed  '         AAAAAaaaaaaaaaa!' in 00:00:00.0093168
//    17. Failed  '        AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
//    18. Failed  '       AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
//    19. Failed  '      AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
//    20. Failed  '     AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
//    21. Failed  '    AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372
Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim sw As Stopwatch
        Dim addresses() As String = {"AAAAAAAAAAA@contoso.com",
                                   "AAAAAAAAAAaaaaaaaaaa!@contoso.com"}
        ' The following regular expression should not actually be used to 
        ' validate an email address.
        Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])*$"
        Dim input As String

        For Each address In addresses
            Dim mailBox As String = address.Substring(0, address.IndexOf("@"))
            Dim index As Integer = 0
            For ctr As Integer = mailBox.Length - 1 To 0 Step -1
                index += 1
                input = mailBox.Substring(ctr, index)
                sw = Stopwatch.StartNew()
                Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
                sw.Stop()
                if m.Success Then
                    Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
                                      index, m.Value, sw.Elapsed)
                Else
                    Console.WriteLine("{0,2}. Failed  '{1,25}' in {2}",
                                      index, input, sw.Elapsed)
                End If
            Next
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays output similar to the following:
'     1. Matched '                        A' in 00:00:00.0007122
'     2. Matched '                       AA' in 00:00:00.0000282
'     3. Matched '                      AAA' in 00:00:00.0000042
'     4. Matched '                     AAAA' in 00:00:00.0000038
'     5. Matched '                    AAAAA' in 00:00:00.0000042
'     6. Matched '                   AAAAAA' in 00:00:00.0000042
'     7. Matched '                  AAAAAAA' in 00:00:00.0000042
'     8. Matched '                 AAAAAAAA' in 00:00:00.0000087
'     9. Matched '                AAAAAAAAA' in 00:00:00.0000045
'    10. Matched '               AAAAAAAAAA' in 00:00:00.0000045
'    11. Matched '              AAAAAAAAAAA' in 00:00:00.0000045
'    
'     1. Failed  '                        !' in 00:00:00.0000447
'     2. Failed  '                       a!' in 00:00:00.0000071
'     3. Failed  '                      aa!' in 00:00:00.0000071
'     4. Failed  '                     aaa!' in 00:00:00.0000061
'     5. Failed  '                    aaaa!' in 00:00:00.0000081
'     6. Failed  '                   aaaaa!' in 00:00:00.0000126
'     7. Failed  '                  aaaaaa!' in 00:00:00.0000359
'     8. Failed  '                 aaaaaaa!' in 00:00:00.0000414
'     9. Failed  '                aaaaaaaa!' in 00:00:00.0000758
'    10. Failed  '               aaaaaaaaa!' in 00:00:00.0001462
'    11. Failed  '              aaaaaaaaaa!' in 00:00:00.0002885
'    12. Failed  '             Aaaaaaaaaaa!' in 00:00:00.0005780
'    13. Failed  '            AAaaaaaaaaaa!' in 00:00:00.0011628
'    14. Failed  '           AAAaaaaaaaaaa!' in 00:00:00.0022851
'    15. Failed  '          AAAAaaaaaaaaaa!' in 00:00:00.0045864
'    16. Failed  '         AAAAAaaaaaaaaaa!' in 00:00:00.0093168
'    17. Failed  '        AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
'    18. Failed  '       AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
'    19. Failed  '      AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
'    20. Failed  '     AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
'    21. Failed  '    AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372

如上述範例的輸出所示,正則表達式引擎會以大約相同的時間間隔處理有效的電子郵件別名,而不論其長度為何。 另一方面,當幾乎有效的電子郵件位址有五個字元以上時,處理字元串中每個額外字元的時間大約會加倍。 因此,幾乎有效的 28 個字符字串需要一個多小時才能處理,而且幾乎有效的 33 個字符字串需要將近一天的時間來處理。

由於此正則表達式只藉由考慮要比對的輸入格式來開發,因此無法考慮不符合模式的輸入。 由於這個疏忽,可能允許幾乎符合正則表達式模式的不受限輸入,從而顯著降低效能。

若要解決此問題,您可以執行下列動作:

  • 開發模式時,您應該考慮回溯如何影響正則表達式引擎的效能,特別是如果您的正則表達式是設計來處理不受限制的輸入時。 如需詳細資訊,請參閱 掌控回溯 一節。

  • 徹底使用無效、近乎有效及有效的輸入值測試您的正則表示式。 您可以使用 Rex 來隨機生成特定正則表達式的輸入。 Rex 是來自 Microsoft Research 的正規表示式探索工具。

適當地處理物件實例化

的核心。NET 的正規表示式物件模型是代表正則表示式引擎的 System.Text.RegularExpressions.Regex 類別。 通常,影響正則表達式效能的單一最大因素就是使用 Regex 引擎的方式。 定義正則表達式牽涉到正則表達式引擎與正則表達式模式緊密結合。 這個結合過程的成本很高,無論是通過將正則表達式模式傳遞給其構造函數來具現化 Regex 物件,還是通過將正則表達式模式和待分析的字串傳遞給靜態方法來呼叫。

備註

如需詳細討論使用解譯和編譯正則表達式對效能的影響,請參閱部落格文章 優化正則表達式效能,第二部分:掌控回溯

您可以將正規表示式引擎與特定的正規表示式模式結合,然後使用引擎以數種方式比對文字:

  • 您可以呼叫靜態模式比對方法,例如 Regex.Match(String, String)。 此方法不需要具現化正則表達式物件。

  • 您可以具現化 Regex 物件,並呼叫解譯正則表達式的實例模式比對方法,這是將正則表達式引擎系結至正則表達式模式的預設方法。 會發生在實例化 Regex 物件時,若未包含 options 旗標的 Compiled 參數。

  • 您可以具現化 Regex 物件,並呼叫來源產生正則表達式的實例模式比對方法。 在大部分情況下,建議使用這項技術。 若要這樣做,請將 GeneratedRegexAttribute 屬性放在傳回 Regex的部分方法上。

  • 您可以具現化 Regex 物件,並呼叫已編譯正則表達式的實例模式比對方法。 當一個包含 Regex 標誌的 options 參數被用於實例化 Compiled 物件時,正規表達式物件就代表了已編譯的模式。

呼叫正則表示式比對方法的特定方式可能會影響應用程式的效能。 下列各節將討論何時使用靜態方法呼叫、來源產生的正則表示式、解譯的正則表達式,以及編譯的正則表達式,以改善應用程式的效能。

這很重要

如果方法呼叫中重複使用相同的正則表達式,或應用程式大量使用正則表達式物件,則方法呼叫的形式(靜態、解譯、來源產生、編譯)會影響效能。

靜態正則表達式

建議使用靜態正則表達式方法,來替代反覆建立相同正則表達式物件的方式。 與正則表達式物件使用的正則表示式模式不同,正則表示式引擎會在內部快取靜態方法呼叫中使用的模式裡的作業代碼(opcodes)或編譯後的通用中間語言(CIL)。

例如,事件處理程式經常呼叫另一個方法來驗證用戶輸入。 此範例會反映在下列程式代碼中,其中 Button 控件的 Click 事件是用來呼叫名為 IsValidCurrency的方法,它會檢查使用者是否已輸入貨幣符號,後面接著至少一個小數位數。

public void OKButton_Click(object sender, EventArgs e)
{
   if (! String.IsNullOrEmpty(sourceCurrency.Text))
      if (RegexLib.IsValidCurrency(sourceCurrency.Text))
         PerformConversion();
      else
         status.Text = "The source currency value is invalid.";
}
Public Sub OKButton_Click(sender As Object, e As EventArgs) _
           Handles OKButton.Click

    If Not String.IsNullOrEmpty(sourceCurrency.Text) Then
        If RegexLib.IsValidCurrency(sourceCurrency.Text) Then
            PerformConversion()
        Else
            status.Text = "The source currency value is invalid."
        End If
    End If
End Sub

下列範例顯示 IsValidCurrency 方法的無效率實作:

備註

每個方法呼叫都會使用相同模式重新初始化 Regex 物件。 接著,這表示每次呼叫 方法時,都必須重新編譯正則表達式模式。

using System;
using System.Text.RegularExpressions;

public class RegexLib
{
   public static bool IsValidCurrency(string currencyValue)
   {
      string pattern = @"\p{Sc}+\s*\d+";
      Regex currencyRegex = new Regex(pattern);
      return currencyRegex.IsMatch(currencyValue);
   }
}
Imports System.Text.RegularExpressions

Public Module RegexLib
    Public Function IsValidCurrency(currencyValue As String) As Boolean
        Dim pattern As String = "\p{Sc}+\s*\d+"
        Dim currencyRegex As New Regex(pattern)
        Return currencyRegex.IsMatch(currencyValue)
    End Function
End Module

您應該將上述效率不佳的程式代碼取代為對靜態 Regex.IsMatch(String, String) 方法的呼叫。 此方法不需要在每次您想要呼叫模式比對方法時具現化 Regex 物件,並讓正則表達式引擎從其快取擷取已編譯的正則表達式版本。

using System;
using System.Text.RegularExpressions;

public class RegexLib2
{
   public static bool IsValidCurrency(string currencyValue)
   {
      string pattern = @"\p{Sc}+\s*\d+";
      return Regex.IsMatch(currencyValue, pattern);
   }
}
Imports System.Text.RegularExpressions

Public Module RegexLib
    Public Function IsValidCurrency(currencyValue As String) As Boolean
        Dim pattern As String = "\p{Sc}+\s*\d+"
        Return Regex.IsMatch(currencyValue, pattern)
    End Function
End Module

根據預設,會快取最近 15 個最近使用的靜態正則表示式模式。 對於需要大量快取靜態正則表達式的應用程式,可以藉由設定 Regex.CacheSize 屬性來調整快取的大小。

此範例中使用的正則表達式 \p{Sc}+\s*\d+ 會確認輸入字串具有貨幣符號和至少一個十進位數。 此模式定義如下表所示:

圖案 說明
\p{Sc}+ 比對 Unicode 符號或貨幣類別中的一個或多個字元。
\s* 可以比對零個或多個空白字元。
\d+ 比對一個或多個十進位數字。

解譯的、來源產生的與編譯的正則表達式

正規表示式模式如果未透過 Compiled 選項規格系結至正規表示式引擎,則會被 解釋為。 具現化正則表示式物件時,正則表達式引擎會將正則表示式轉換成一組作業程序代碼。 呼叫實例方法時,作業程式代碼會轉換成 CIL,並由 JIT 編譯程式執行。 同樣地,當呼叫靜態正則表達式方法且快取中找不到正則表示式時,正則表達式引擎會將正則表達式轉換成一組作業程序代碼,並將其儲存在快取中。 然後將這些作業程式代碼轉換成 CIL,讓 JIT 編譯程式可以執行它們。 解譯的正則表示式降低了啟動時間,但代價是執行時間較慢。 由於這個過程,當在少數方法呼叫中使用正規表達式時,最好使用它們,或者在正規表達式方法的確切呼叫數目未知但預期會很小的情況下使用它們。 隨著方法呼叫次數的增加,由於執行速度較慢,啟動時間縮短所帶來的效能提升將逐漸減少。

透過 Compiled 選項的規格系結至正則表示式引擎的正規表示式模式,編譯。 因此,當正則表達式物件具現化,或呼叫靜態正則表示式方法且快取中找不到正則表達式時,正則表達式引擎會將正則表達式轉換成一組中繼作業程序代碼。 然後,這些程式代碼會轉換成 CIL。 呼叫 方法時,JIT 編譯程式會執行 CIL。 相較於解譯的正則表達式,編譯的正則表示式會增加啟動時間,但執行個別模式比對方法的速度會更快。 因此,編譯正則表達式所產生的效能優點會隨著呼叫的正則表達式方法數目而增加。

透過用 Regex 屬性裝飾並與 GeneratedRegexAttribute傳回方法綁定的正則表示式模式,會產生 原始。 插入編譯程式的來源產生器會以 C# 程式代碼的形式發出自定義 Regex衍生實作,其邏輯類似於 CIL 中 RegexOptions.Compiled 發出的邏輯。 您可以獲得 RegexOptions.Compiled 的所有數據吞吐效能優勢(實際上更多)和 Regex.CompileToAssembly的啟動優勢,而不具備 CompileToAssembly的複雜性。 發出的來源是專案的一部分,這表示它也可以輕易地檢視和偵錯。

總結一下,我們建議您:

  • 當您使用較不頻繁的正規表示式來呼叫正規表示式方法時,請使用 解譯 正規表示式。
  • 如果您在 C# 中使用 ,並且參數是編譯時已知的,而您相對經常使用特定的正則表達式,請使用 Regex 正則表達式。
  • 使用 .NET 6 或更早版本時,如果您經常使用特定的正則表達式來呼叫正則表達式方法,應使用 編譯 的正則表達式。

要確定解譯正則表達式的執行速度較慢何時抵消其啟動時間縮短的優勢是困難的。 也很難判斷來源產生或編譯正則表達式啟動時間較慢的臨界值,超過其較快的執行速度。 臨界值取決於各種因素,包括正則表達式的複雜度及其處理的特定數據。 若要判斷哪些正則表示式為您的特定應用程式案例提供最佳效能,您可以使用 Stopwatch 類別來比較其運行時間。

下列範例會比較在讀取前 10 個句子時編譯、產生來源和解譯正則表達式的效能,以及讀取威廉 D. 古思里之 的 Magna Carta 和其他位址文字中的所有句子時。 如範例輸出所示,當只有10個呼叫正則表達式比對方法時,解譯或來源產生的正則表達式可提供比編譯的正則表達式更好的效能。 不過,編譯的正則表達式會在進行大量呼叫時提供更佳的效能(在此案例中為超過13,000個)。

const string Pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";

static readonly HttpClient s_client = new();

[GeneratedRegex(Pattern, RegexOptions.Singleline)]
private static partial Regex GeneratedRegex();

public async static Task RunIt()
{
    Stopwatch sw;
    Match match;
    int ctr;

    string text =
            await s_client.GetStringAsync("https://www.gutenberg.org/cache/epub/64197/pg64197.txt");

    // Read first ten sentences with interpreted regex.
    Console.WriteLine("10 Sentences with Interpreted Regex:");
    sw = Stopwatch.StartNew();
    Regex int10 = new(Pattern, RegexOptions.Singleline);
    match = int10.Match(text);
    for (ctr = 0; ctr <= 9; ctr++)
    {
        if (match.Success)
            // Do nothing with the match except get the next match.
            match = match.NextMatch();
        else
            break;
    }
    sw.Stop();
    Console.WriteLine($"   {ctr} matches in {sw.Elapsed}");

    // Read first ten sentences with compiled regex.
    Console.WriteLine("10 Sentences with Compiled Regex:");
    sw = Stopwatch.StartNew();
    Regex comp10 = new Regex(Pattern,
                 RegexOptions.Singleline | RegexOptions.Compiled);
    match = comp10.Match(text);
    for (ctr = 0; ctr <= 9; ctr++)
    {
        if (match.Success)
            // Do nothing with the match except get the next match.
            match = match.NextMatch();
        else
            break;
    }
    sw.Stop();
    Console.WriteLine($"   {ctr} matches in {sw.Elapsed}");

    // Read first ten sentences with source-generated regex.
    Console.WriteLine("10 Sentences with Source-generated Regex:");
    sw = Stopwatch.StartNew();

    match = GeneratedRegex().Match(text);
    for (ctr = 0; ctr <= 9; ctr++)
    {
        if (match.Success)
            // Do nothing with the match except get the next match.
            match = match.NextMatch();
        else
            break;
    }
    sw.Stop();
    Console.WriteLine($"   {ctr} matches in {sw.Elapsed}");

    // Read all sentences with interpreted regex.
    Console.WriteLine("All Sentences with Interpreted Regex:");
    sw = Stopwatch.StartNew();
    Regex intAll = new(Pattern, RegexOptions.Singleline);
    match = intAll.Match(text);
    int matches = 0;
    while (match.Success)
    {
        matches++;
        // Do nothing with the match except get the next match.
        match = match.NextMatch();
    }
    sw.Stop();
    Console.WriteLine($"   {matches:N0} matches in {sw.Elapsed}");

    // Read all sentences with compiled regex.
    Console.WriteLine("All Sentences with Compiled Regex:");
    sw = Stopwatch.StartNew();
    Regex compAll = new(Pattern,
                    RegexOptions.Singleline | RegexOptions.Compiled);
    match = compAll.Match(text);
    matches = 0;
    while (match.Success)
    {
        matches++;
        // Do nothing with the match except get the next match.
        match = match.NextMatch();
    }
    sw.Stop();
    Console.WriteLine($"   {matches:N0} matches in {sw.Elapsed}");

    // Read all sentences with source-generated regex.
    Console.WriteLine("All Sentences with Source-generated Regex:");
    sw = Stopwatch.StartNew();
    match = GeneratedRegex().Match(text);
    matches = 0;
    while (match.Success)
    {
        matches++;
        // Do nothing with the match except get the next match.
        match = match.NextMatch();
    }
    sw.Stop();
    Console.WriteLine($"   {matches:N0} matches in {sw.Elapsed}");

    return;
}
/* The example displays output similar to the following:

   10 Sentences with Interpreted Regex:
       10 matches in 00:00:00.0104920
   10 Sentences with Compiled Regex:
       10 matches in 00:00:00.0234604
   10 Sentences with Source-generated Regex:
       10 matches in 00:00:00.0060982
   All Sentences with Interpreted Regex:
       3,427 matches in 00:00:00.1745455
   All Sentences with Compiled Regex:
       3,427 matches in 00:00:00.0575488
   All Sentences with Source-generated Regex:
       3,427 matches in 00:00:00.2698670
*/

範例中使用的正則表示式模式 \b(\w+((\r?\n)|,?\s))*\w+[.?:;!],定義如下表所示:

圖案 說明
\b 在字詞邊界開始比對
\w+ 比對一或多個單字字元。
(\r?\n)|,?\s) 比對零或一個回車符後面接著換行字元,或零或一個逗號後面接著空白字元。
(\w+((\r?\n)|,?\s))* 比對零或多次出現的一個或多個文字字元,後面接著零個或一個歸位字元和換行符,或零或一個逗號後面接著空格符。
\w+ 比對一或多個單字字元。
[.?:;!] 比對句點、問號、冒號、分號或驚嘆號。

負責回溯

一般而言,正則表達式引擎會使用線性進展來移動輸入字串,並將其與正則表達式模式進行比較。 不過,當正則表達式模式中使用 *+? 等不確定數量值時,正則表達式引擎可能會放棄部分成功的部分相符專案,並返回先前儲存的狀態,以搜尋整個模式的成功比對。 此程序稱為回溯。

小提示

如需回溯的詳細資訊,請參閱 正則表示式行為的詳細數據回溯。 如需回溯的詳細討論,請參閱 .NET 7 中的 正則表達式改進優化正則表達式效能表現 的相關部落格文章。

支援回溯可提供正則表達式的強大功能和彈性。 它也會將控制正則表達式引擎作業的責任放在正則表達式開發人員手中。 由於開發人員通常不知道此責任,因此他們誤用回溯或依賴過度回溯,在降低正則表達式效能方面通常扮演最重要的角色。 在最壞的情況下,輸入字串中每個額外字元的運行時間可以加倍。 事實上,過度使用回溯會使得當輸入幾乎符合正則表達式模式時,很容易建立類似無限迴圈的程序。 正則表達式引擎可能需要數小時甚至數天的時間來處理相對簡短的輸入字串。

應用程式通常會因使用回溯而遭受效能損失,即使回溯對比對並非不可或缺。 例如,正則表達式 \b\p{Lu}\w*\b 符合以大寫字元開頭的所有字組,如下表所示:

圖案 說明
\b 在字詞邊界開始比對
\p{Lu} 比對大寫字元。
\w* 比對零個或多個單字字符。
\b 在字元邊界處停止匹配。

因為字邊界與字元字符既不相同,也不是字元字符的子集,因此在比對字元字符時,正則表達式引擎不可能跨越字邊界。 因此,對於這個正則表達式,回溯永遠都無法促成任何相符項目的整體成功。 它只能降低效能,因為正則表達式引擎被迫在每次成功比對一個單字字元的初步階段時儲存其狀態。

如果您判斷不需要回溯,您可以透過一些方法將其關閉:

  • 藉由設定 RegexOptions.NonBacktracking 選項(在 .NET 7 中引進)。 如需詳細資訊,請參閱 非回溯模式

  • 藉由使用 (?>subexpression) 語言元素,稱為原子組。 下列範例會使用兩個正則表達式來剖析輸入字串。 第一個,\b\p{Lu}\w*\b,依賴回溯。 第二個 \b\p{Lu}(?>\w*)\b功能會停用回溯。 如範例所示的輸出,它們都會產生相同的結果:

    using System;
    using System.Text.RegularExpressions;
    
    public class BackTrack2Example
    {
        public static void Main()
        {
            string input = "This this word Sentence name Capital";
            string pattern = @"\b\p{Lu}\w*\b";
            foreach (Match match in Regex.Matches(input, pattern))
                Console.WriteLine(match.Value);
    
            Console.WriteLine();
    
            pattern = @"\b\p{Lu}(?>\w*)\b";
            foreach (Match match in Regex.Matches(input, pattern))
                Console.WriteLine(match.Value);
        }
    }
    // The example displays the following output:
    //       This
    //       Sentence
    //       Capital
    //
    //       This
    //       Sentence
    //       Capital
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim input As String = "This this word Sentence name Capital"
            Dim pattern As String = "\b\p{Lu}\w*\b"
            For Each match As Match In Regex.Matches(input, pattern)
                Console.WriteLine(match.Value)
            Next
            Console.WriteLine()
    
            pattern = "\b\p{Lu}(?>\w*)\b"
            For Each match As Match In Regex.Matches(input, pattern)
                Console.WriteLine(match.Value)
            Next
        End Sub
    End Module
    ' The example displays the following output:
    '       This
    '       Sentence
    '       Capital
    '       
    '       This
    '       Sentence
    '       Capital
    

在許多情況下,回溯對於比對正則表達式模式與輸入文字而言是必要的。 不過,過度回溯可能會嚴重降低效能,並產生應用程式已停止回應的印象。 特別是當數量值是巢狀的,且符合外部子表達式的文字是符合內部子表達式的文字子集時,就會發生這個問題。

警告

除了避免過度回溯之外,您應該使用逾時功能來確保過度回溯不會嚴重降低正則表達式效能。 如需詳細資訊,請參閱 使用逾時值 一節。

例如,正則表達式模式 ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ 是要比對由至少包含一個字母或數字的字元組成的零件編號。 任何額外的字元都可以由英數位元、連字元、底線或句點組成,不過最後一個字元必須是英數位元。 美元符號會結束零件編號。 在某些情況下,由於數量值是巢狀的,而且子表達式 [0-9A-Z] 是子表達式 [-.\w]*子表達式的子集,所以此正則表達式模式可能會呈現效能不佳。

在這些情況下,您可以移除巢狀數量詞,並以零寬度先行或後行斷言取代外部子表達式,以優化正則表達式效能。 Lookahead 和 lookbehind 判斷提示是錨點。 它們不會移動輸入字串中的指標,而是往前或往後查看,以檢查指定的條件是否符合。 例如,部分編號正則表示式可以重寫為 ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$。 這個正規表示式模式的定義如下表所示:

圖案 說明
^ 在輸入字串的開頭開始比對。
[0-9A-Z] 匹配字母或數字字符。 元件編號至少必須包含這個字元。
[-.\w]* 匹配零次或多次出現的任何單字字元、連字符或句點。
\$ 比對貨幣符號。
(?<=[0-9A-Z]) 檢查結尾的美元符號,以確保它前面的字元是英文字母或數字。
$ 在輸入字串的結尾結束匹配。

下列範例說明使用此正規表示式來比對包含可能零件編號的陣列:

using System;
using System.Text.RegularExpressions;

public class BackTrack4Example
{
    public static void Main()
    {
        string pattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$";
        string[] partNos = { "A1C$", "A4", "A4$", "A1603D$", "A1603D#" };

        foreach (var input in partNos)
        {
            Match match = Regex.Match(input, pattern);
            if (match.Success)
                Console.WriteLine(match.Value);
            else
                Console.WriteLine("Match not found.");
        }
    }
}
// The example displays the following output:
//       A1C$
//       Match not found.
//       A4$
//       A1603D$
//       Match not found.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$"
        Dim partNos() As String = {"A1C$", "A4", "A4$", "A1603D$",
                                    "A1603D#"}

        For Each input As String In partNos
            Dim match As Match = Regex.Match(input, pattern)
            If match.Success Then
                Console.WriteLine(match.Value)
            Else
                Console.WriteLine("Match not found.")
            End If
        Next
    End Sub
End Module
' The example displays the following output:
'       A1C$
'       Match not found.
'       A4$
'       A1603D$
'       Match not found.

.NET 中的正則表達式語言包含下列語言元素,可用來消除巢狀數量值。 如需詳細資訊,請參閱 群組建構

語言元素 說明
(?= subexpression ) 零寬度正向外觀。 向前看目前的位置,以判斷 subexpression 是否符合輸入字串。
(?! subexpression ) 零寬度負向外觀。 查看目前的位置,以判斷 subexpression 是否不符合輸入字串。
(?<= subexpression ) 零寬度正向外觀。 從當前位置回溯尋找,以判斷 subexpression 是否符合輸入字串。
(?<! subexpression ) 零寬度負向後查找。 在目前位置回溯,以判斷 subexpression 是否不符合輸入字串。

使用逾時時間值

如果您的正則表達式處理幾乎符合正則表達式模式的輸入,它通常會依賴過多的回溯,這會大幅影響其效能。 除了仔細考慮使用回溯並測試正則表達式與接近相符的輸入,您應該一律設定逾時值,以在發生時將過度回溯的效果降到最低。

正則表達式的超時時間間隔定義了正則表示式引擎在逾時之前尋找單一相符項目的時間上限。根據正則表示式模式和輸入文字,執行時間可能會超過指定的超時時間間隔,但不會花費超過指定超時時間間隔的時間進行回溯。 預設超時時間間隔為 Regex.InfiniteMatchTimeout,這表示正則表達式不會超時。您可以覆寫此值並定義超時時間間隔,如下所示:

如果您已定義超時時間間隔,且在該間隔結束時仍找不到匹配項目,正則表達式方法會拋出 RegexMatchTimeoutException 例外狀況。 在例外狀況處理程式中,您可以選擇使用較長的時間間隔重試比對、放棄比對嘗試,並假設沒有相符專案,或放棄比對嘗試,並記錄例外狀況資訊以供日後分析。

下列範例會定義一個 GetWordData 方法,以 350 毫秒的超時時間間隔具現化正則表示式,以計算文字檔中單字的字數和平均字元數。 如果比對作業逾時,逾時間隔會增加 350 毫秒,並重新實例化 Regex 物件。 如果新的超時時間間隔超過一秒,方法會將例外狀況重新拋出給呼叫端。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

public class TimeoutExample
{
    public static void Main()
    {
        RegexUtilities util = new RegexUtilities();
        string title = "Doyle - The Hound of the Baskervilles.txt";
        try
        {
            var info = util.GetWordData(title);
            Console.WriteLine($"Words:               {info.Item1:N0}");
            Console.WriteLine($"Average Word Length: {info.Item2:N2} characters");
        }
        catch (IOException e)
        {
            Console.WriteLine($"IOException reading file '{title}'");
            Console.WriteLine(e.Message);
        }
        catch (RegexMatchTimeoutException e)
        {
            Console.WriteLine($"The operation timed out after {e.MatchTimeout.TotalMilliseconds:N0} milliseconds");
        }
    }
}

public class RegexUtilities
{
    public Tuple<int, double> GetWordData(string filename)
    {
        const int MAX_TIMEOUT = 1000;   // Maximum timeout interval in milliseconds.
        const int INCREMENT = 350;      // Milliseconds increment of timeout.

        List<string> exclusions = new List<string>(new string[] { "a", "an", "the" });
        int[] wordLengths = new int[29];        // Allocate an array of more than ample size.
        string input = null;
        StreamReader sr = null;
        try
        {
            sr = new StreamReader(filename);
            input = sr.ReadToEnd();
        }
        catch (FileNotFoundException e)
        {
            string msg = String.Format("Unable to find the file '{0}'", filename);
            throw new IOException(msg, e);
        }
        catch (IOException e)
        {
            throw new IOException(e.Message, e);
        }
        finally
        {
            if (sr != null) sr.Close();
        }

        int timeoutInterval = INCREMENT;
        bool init = false;
        Regex rgx = null;
        Match m = null;
        int indexPos = 0;
        do
        {
            try
            {
                if (!init)
                {
                    rgx = new Regex(@"\b\w+\b", RegexOptions.None,
                                    TimeSpan.FromMilliseconds(timeoutInterval));
                    m = rgx.Match(input, indexPos);
                    init = true;
                }
                else
                {
                    m = m.NextMatch();
                }
                if (m.Success)
                {
                    if (!exclusions.Contains(m.Value.ToLower()))
                        wordLengths[m.Value.Length]++;

                    indexPos += m.Length + 1;
                }
            }
            catch (RegexMatchTimeoutException e)
            {
                if (e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT)
                {
                    timeoutInterval += INCREMENT;
                    init = false;
                }
                else
                {
                    // Rethrow the exception.
                    throw;
                }
            }
        } while (m.Success);

        // If regex completed successfully, calculate number of words and average length.
        int nWords = 0;
        long totalLength = 0;

        for (int ctr = wordLengths.GetLowerBound(0); ctr <= wordLengths.GetUpperBound(0); ctr++)
        {
            nWords += wordLengths[ctr];
            totalLength += ctr * wordLengths[ctr];
        }
        return new Tuple<int, double>(nWords, totalLength / nWords);
    }
}
Imports System.Collections.Generic
Imports System.IO
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim util As New RegexUtilities()
        Dim title As String = "Doyle - The Hound of the Baskervilles.txt"
        Try
            Dim info = util.GetWordData(title)
            Console.WriteLine("Words:               {0:N0}", info.Item1)
            Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2)
        Catch e As IOException
            Console.WriteLine("IOException reading file '{0}'", title)
            Console.WriteLine(e.Message)
        Catch e As RegexMatchTimeoutException
            Console.WriteLine("The operation timed out after {0:N0} milliseconds",
                              e.MatchTimeout.TotalMilliseconds)
        End Try
    End Sub
End Module

Public Class RegexUtilities
    Public Function GetWordData(filename As String) As Tuple(Of Integer, Double)
        Const MAX_TIMEOUT As Integer = 1000  ' Maximum timeout interval in milliseconds.
        Const INCREMENT As Integer = 350     ' Milliseconds increment of timeout.

        Dim exclusions As New List(Of String)({"a", "an", "the"})
        Dim wordLengths(30) As Integer        ' Allocate an array of more than ample size.
        Dim input As String = Nothing
        Dim sr As StreamReader = Nothing
        Try
            sr = New StreamReader(filename)
            input = sr.ReadToEnd()
        Catch e As FileNotFoundException
            Dim msg As String = String.Format("Unable to find the file '{0}'", filename)
            Throw New IOException(msg, e)
        Catch e As IOException
            Throw New IOException(e.Message, e)
        Finally
            If sr IsNot Nothing Then sr.Close()
        End Try

        Dim timeoutInterval As Integer = INCREMENT
        Dim init As Boolean = False
        Dim rgx As Regex = Nothing
        Dim m As Match = Nothing
        Dim indexPos As Integer = 0
        Do
            Try
                If Not init Then
                    rgx = New Regex("\b\w+\b", RegexOptions.None,
                                    TimeSpan.FromMilliseconds(timeoutInterval))
                    m = rgx.Match(input, indexPos)
                    init = True
                Else
                    m = m.NextMatch()
                End If
                If m.Success Then
                    If Not exclusions.Contains(m.Value.ToLower()) Then
                        wordLengths(m.Value.Length) += 1
                    End If
                    indexPos += m.Length + 1
                End If
            Catch e As RegexMatchTimeoutException
                If e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT Then
                    timeoutInterval += INCREMENT
                    init = False
                Else
                    ' Rethrow the exception.
                    Throw
                End If
            End Try
        Loop While m.Success

        ' If regex completed successfully, calculate number of words and average length.
        Dim nWords As Integer
        Dim totalLength As Long

        For ctr As Integer = wordLengths.GetLowerBound(0) To wordLengths.GetUpperBound(0)
            nWords += wordLengths(ctr)
            totalLength += ctr * wordLengths(ctr)
        Next
        Return New Tuple(Of Integer, Double)(nWords, totalLength / nWords)
    End Function
End Class

僅在必要時擷取

.NET 中的正則表達式支援群組建構,可讓您將正則表示式模式分組成一或多個子表達式。 .NET 正則表示式語言中最常使用的群組建構是 (子運算式),其會定義編號擷取群組,(?<名稱>子表達式),其定義具名擷取群組。 群組建構對於建立反向參考和定義套用數量值之子表達式而言非常重要。

不過,使用這些語言元素是有代價的。 它們會導致由 GroupCollection 屬性傳回的 Match.Groups 物件填入最新的未命名或具名擷取內容。 如果單一群組建構在輸入字串中擷取了多個子字串,這些子字串會填入特定擷取群組的 CaptureCollection 屬性所傳回的 Group.Captures 物件中,該物件包含多個 Capture 物件。

通常,群組結構用於正則表示式中,目的是將量詞應用於它們。 這些子表達式所擷取的群組稍後不會使用。 例如,正則表達式 \b(\w+[;,]?\s?)+[.?!] 是設計來擷取整個句子。 下表描述此正則表示式模式中的語言專案,以及它們對 Match 物件的 Match.GroupsGroup.Captures 集合的影響:

圖案 說明
\b 在字詞邊界開始比對
\w+ 比對一或多個單字字元。
[;,]? 比對零或一個逗號或分號。
\s? 比對零或一個空白字元。
(\w+[;,]?\s?)+ 比對一或多個出現的一或多個單字字元,後面接著選擇性逗號或分號,後面接著選擇性空格符。 此模式會定義第一個擷取群組,這是必要的,這樣一來,多個單字字元的組合(也就是單字)後面接著選擇性標點符號,將會重複直到正規表示式引擎到達句子結尾為止。
[.?!] 比對句點、問號或驚嘆號。

如下列範例所示,當找到匹配項目時,GroupCollectionCaptureCollection 物件都會填入匹配中的擷取。 在此情況下,擷取群組 (\w+[;,]?\s?) 存在,以便將 + 數量值套用至該群組,讓正則表達式模式能夠比對句子中的每個單字。 否則,它會比對句子中的最後一個字。

using System;
using System.Text.RegularExpressions;

public class Group1Example
{
    public static void Main()
    {
        string input = "This is one sentence. This is another.";
        string pattern = @"\b(\w+[;,]?\s?)+[.?!]";

        foreach (Match match in Regex.Matches(input, pattern))
        {
            Console.WriteLine($"Match: '{match.Value}' at index {match.Index}.");
            int grpCtr = 0;
            foreach (Group grp in match.Groups)
            {
                Console.WriteLine($"   Group {grpCtr}: '{grp.Value}' at index {grp.Index}.");
                int capCtr = 0;
                foreach (Capture cap in grp.Captures)
                {
                    Console.WriteLine($"      Capture {capCtr}: '{cap.Value}' at {cap.Index}.");
                    capCtr++;
                }
                grpCtr++;
            }
            Console.WriteLine();
        }
    }
}
// The example displays the following output:
//       Match: 'This is one sentence.' at index 0.
//          Group 0: 'This is one sentence.' at index 0.
//             Capture 0: 'This is one sentence.' at 0.
//          Group 1: 'sentence' at index 12.
//             Capture 0: 'This ' at 0.
//             Capture 1: 'is ' at 5.
//             Capture 2: 'one ' at 8.
//             Capture 3: 'sentence' at 12.
//
//       Match: 'This is another.' at index 22.
//          Group 0: 'This is another.' at index 22.
//             Capture 0: 'This is another.' at 22.
//          Group 1: 'another' at index 30.
//             Capture 0: 'This ' at 22.
//             Capture 1: 'is ' at 27.
//             Capture 2: 'another' at 30.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim input As String = "This is one sentence. This is another."
        Dim pattern As String = "\b(\w+[;,]?\s?)+[.?!]"

        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("Match: '{0}' at index {1}.",
                              match.Value, match.Index)
            Dim grpCtr As Integer = 0
            For Each grp As Group In match.Groups
                Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                                  grpCtr, grp.Value, grp.Index)
                Dim capCtr As Integer = 0
                For Each cap As Capture In grp.Captures
                    Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                      capCtr, cap.Value, cap.Index)
                    capCtr += 1
                Next
                grpCtr += 1
            Next
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays the following output:
'       Match: 'This is one sentence.' at index 0.
'          Group 0: 'This is one sentence.' at index 0.
'             Capture 0: 'This is one sentence.' at 0.
'          Group 1: 'sentence' at index 12.
'             Capture 0: 'This ' at 0.
'             Capture 1: 'is ' at 5.
'             Capture 2: 'one ' at 8.
'             Capture 3: 'sentence' at 12.
'       
'       Match: 'This is another.' at index 22.
'          Group 0: 'This is another.' at index 22.
'             Capture 0: 'This is another.' at 22.
'          Group 1: 'another' at index 30.
'             Capture 0: 'This ' at 22.
'             Capture 1: 'is ' at 27.
'             Capture 2: 'another' at 30.

當您只使用子表達式來將數量值套用至它們,而且您對擷取的文字不感興趣時,您應該停用群組擷取。 例如,(?:subexpression) 語言元素能防止應用的群組擷取匹配的子字串。 在下列範例中,上一個範例中的正則表示式模式會變更為 \b(?:\w+[;,]?\s?)+[.?!]。 如輸出所示,它可防止正則表達式引擎填入 GroupCollectionCaptureCollection 集合:

using System;
using System.Text.RegularExpressions;

public class Group2Example
{
    public static void Main()
    {
        string input = "This is one sentence. This is another.";
        string pattern = @"\b(?:\w+[;,]?\s?)+[.?!]";

        foreach (Match match in Regex.Matches(input, pattern))
        {
            Console.WriteLine($"Match: '{match.Value}' at index {match.Index}.");
            int grpCtr = 0;
            foreach (Group grp in match.Groups)
            {
                Console.WriteLine($"   Group {grpCtr}: '{grp.Value}' at index {grp.Index}.");
                int capCtr = 0;
                foreach (Capture cap in grp.Captures)
                {
                    Console.WriteLine($"      Capture {capCtr}: '{cap.Value}' at {cap.Index}.");
                    capCtr++;
                }
                grpCtr++;
            }
            Console.WriteLine();
        }
    }
}
// The example displays the following output:
//       Match: 'This is one sentence.' at index 0.
//          Group 0: 'This is one sentence.' at index 0.
//             Capture 0: 'This is one sentence.' at 0.
//
//       Match: 'This is another.' at index 22.
//          Group 0: 'This is another.' at index 22.
//             Capture 0: 'This is another.' at 22.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim input As String = "This is one sentence. This is another."
        Dim pattern As String = "\b(?:\w+[;,]?\s?)+[.?!]"

        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("Match: '{0}' at index {1}.",
                              match.Value, match.Index)
            Dim grpCtr As Integer = 0
            For Each grp As Group In match.Groups
                Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                                  grpCtr, grp.Value, grp.Index)
                Dim capCtr As Integer = 0
                For Each cap As Capture In grp.Captures
                    Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                      capCtr, cap.Value, cap.Index)
                    capCtr += 1
                Next
                grpCtr += 1
            Next
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays the following output:
'       Match: 'This is one sentence.' at index 0.
'          Group 0: 'This is one sentence.' at index 0.
'             Capture 0: 'This is one sentence.' at 0.
'       
'       Match: 'This is another.' at index 22.
'          Group 0: 'This is another.' at index 22.
'             Capture 0: 'This is another.' at 22.

您可以使用下列其中一種方式停用擷取:

  • 使用 (?:subexpression) 語言元素。 此元素可防止擷取套用群組中相符的子字串。 它不會停用任何巢狀群組中的子字串擷取。

  • 使用 ExplicitCapture 選項。 它會停用正則表達式模式中的所有未命名或隱含擷取。 當您使用此選項時,只能擷取符合以 (?<name>subexpression) 語言項目定義之具名群組的子字串。 ExplicitCapture 旗標可以傳遞至 options 類別建構函式的 Regex 參數或 options 靜態比對方法的 Regex 參數。

  • 使用 n 選項在 (?imnsx) 語言元素中。 此選項會停用從正則表達式模式中元素出現的位置開始的所有未命名或隱式捕獲。 擷取會停用直到模式結尾,或直到 (-n) 選項啟用未命名或隱含擷取為止。 如需詳細資訊,請參閱 其他建構

  • 使用 n 選項在 (?imnsx:subexpression) 語言元素中。 此選項會停用 subexpression中的所有未命名或隱含擷取。 任何未命名或隱含巢狀擷取群組的擷取也被停用。

執行緒安全

Regex 類別本身是線程安全且不可變的(只讀)。 也就是說,Regex 物件可以在任何線程上建立,並在線程之間共用;您可以從任何線程呼叫比對方法,而且永遠不會改變任何全域狀態。

不過,Match 傳回的結果物件(MatchCollectionRegex)應該在單個線程上使用。 雖然其中許多對象在邏輯上是不可變的,但其實作可能會延遲計算某些結果以改善效能,因此呼叫端必須將存取權串行化。

如果您需要在多個線程上共用 Regex 結果對象,這些物件可以藉由呼叫其同步處理的方法轉換成安全線程實例。 除了列舉器之外,所有正則表達式類別都是執行緒安全,或可以通過同步方法轉換為執行緒安全物件。

枚舉器是唯一的例外。 您必須將對集合列舉器的呼叫序列化。 規則是,如果集合可以同時在多個執行緒上列舉,您應該在列舉器所遍歷集合的根物件上同步列舉方法。

標題 說明
正則表達式行為的詳情 檢查 .NET 中正則表達式引擎的實作。 本文著重於正則表達式的彈性,並說明開發人員負責確保正則表達式引擎的有效率且健全的作業。
回溯 本文說明何謂回溯及其對正則表達式效能的影響,並探討提供回溯替代方法的語言元素。
正則表達式語言 - 快速參考 描述 .NET 中正則表示式語言的元素,並提供每個語言專案詳細文件的連結。