.NET の正規表現に関するベスト プラクティスBest practices for regular expressions in .NET

.NET の正規表現エンジンは、リテラル テキストの比較と照合ではなくパターン一致に基づいてテキストを処理する、完全な機能を備えた強力なツールです。The regular expression engine in .NET is a powerful, full-featured tool that processes text based on pattern matches rather than on comparing and matching literal text. ほとんどの場合は、すばやく効率的にパターン一致が実行されますが、In most cases, it performs pattern matching rapidly and efficiently. 処理が非常に遅く見えることもあります。However, in some cases, the regular expression engine can appear to be very slow. 極端なケースでは、比較的小さな入力の処理に何時間も何日もかかり、応答しなくなったように見えることさえあります。In extreme cases, it can even appear to stop responding as it processes a relatively small input over the course of hours or even days.

ここでは、正規表現で最適なパフォーマンスを確保するためのいくつかのベスト プラクティスの概要を説明します。This topic outlines some of the best practices that developers can adopt to ensure that their regular expressions achieve optimal performance.

入力ソースを考慮に入れるConsider the input source

一般に、正規表現で受け入れられる入力には、制約のある入力と制約のない入力の 2 種類があります。In general, regular expressions can accept two types of input: constrained or unconstrained. 制約のある入力とは、あらかじめ定義された形式に従っている、既知のソースまたは信頼できるソースからのテキストです。Constrained input is text that originates from a known or reliable source and follows a predefined format. 制約のない入力とは、あらかじめ定義された形式や予想される形式に従っていない可能性のある、不確実なソース (Web ユーザーなど) からのテキストです。Unconstrained input is text that originates from an unreliable source, such as a web user, and may not follow a predefined or expected format.

正規表現パターンは、有効な入力に一致するように記述されるのが一般的です。Regular expression patterns are typically written to match valid input. 開発者はまず、対象となるテキストを調査して、そのテキストに一致する正規表現パターンを記述します。That is, developers examine the text that they want to match and then write a regular expression pattern that matches it. 記述が完了すると、そのパターンを複数の有効な入力項目でテストして、修正や改善が必要かどうかを確認します。Developers then determine whether this pattern requires correction or further elaboration by testing it with multiple valid input items. 想定されるすべての有効な入力に一致するようになったら、そのパターンは運用環境で使用する準備が整ったと見なされ、リリースされるアプリケーションに含めることができます。When the pattern matches all presumed valid inputs, it is declared to be production-ready and can be included in a released application. こうして作成された正規表現パターンは、制約のある入力との照合には適していますが、This makes a regular expression pattern suitable for matching constrained input. 制約のない入力との照合に適しているとは言えません。However, it does not make it suitable for matching unconstrained input.

制約のない入力と照合する正規表現は、次の 3 種類のテキストを効率的に処理できなければなりません。To match unconstrained input, a regular expression must be able to efficiently handle three kinds of text:

  • 正規表現パターンに一致するテキスト。Text that matches the regular expression pattern.

  • 正規表現パターンに一致しないテキスト。Text that does not match the regular expression pattern.

  • 正規表現パターンにほぼ一致するテキスト。Text that nearly matches the regular expression pattern.

制約のある入力を処理するために記述された正規表現で特に問題となるのは、最後の種類のテキストです。The last text type is especially problematic for a regular expression that has been written to handle constrained input. その正規表現が広範なバックトラッキングにも依存している場合、一見何の問題もないように見えるテキストの処理に極端に長い時間 (場合によっては何時間も何日も) が費やされる可能性があります。If that regular expression also relies on extensive backtracking, the regular expression engine can spend an inordinate amount of time (in some cases, many hours or days) processing seemingly innocuous text.

警告

次の例では、過度なバックトラッキングを生じる傾向があり、有効な電子メール アドレスを拒否する可能性がある正規表現を使用します。The following example uses a regular expression that is prone to excessive backtracking and that is likely to reject valid email addresses. 電子メールの検証ルーチンで使用しないでください。You should not use it in an email validation routine. 電子メール アドレスを検証する正規表現を使用する場合は、「方法:文字列が有効な電子メール形式であるかどうかを検証する」をご覧ください。If you would like a regular expression that validates email addresses, see How to: Verify that Strings Are in Valid Email Format.

例として、電子メール アドレスのエイリアスを検証するための正規表現について見てみましょう。このような正規表現はよく使用されますが、きわめて大きな問題もはらんでいます。For example, consider a very commonly used but extremely problematic regular expression for validating the alias of an email address. 有効と見なされる電子メール アドレスを処理するために、^[0-9A-Z]([-.\w]*[0-9A-Z])*$ という正規表現を記述したとします。有効な電子メール アドレスは、英数字で始まり、その後に 0 個以上の文字 (英数字、ピリオド、またはハイフン) が続きます。The regular expression ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ is written to process what is considered to be a valid email address, which consists of an alphanumeric character, followed by zero or more characters that can be alphanumeric, periods, or hyphens. また、正規表現は英数字で終了する必要があります。The regular expression must end with an alphanumeric character. この正規表現は、次の例に示すように、有効な入力は簡単に処理できますが、有効に近い入力を処理するときに極端に処理効率が低下します。However, as the following example shows, although this regular expression handles valid input easily, its performance is very inefficient when it is processing nearly valid input.

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

public class Example
{
   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

この出力を見るとわかるように、有効な電子メール エイリアスは、その長さに関係なくほとんど同じ時間で処理されます。As the output from the example shows, the regular expression engine processes the valid email alias in about the same time interval regardless of its length. 一方、有効に近い電子メール アドレスでは、長さが 5 文字を超えると、文字列の文字が 1 文字増えるたびに処理時間が約 2 倍に増加します。On the other hand, when the nearly valid email address has more than five characters, processing time approximately doubles for each additional character in the string. つまり、有効に近い文字列の長さが 28 文字になると処理に 1 時間以上かかり、33 文字になるとほぼ 1 日かかることになります。This means that a nearly valid 28-character string would take over an hour to process, and a nearly valid 33-character string would take nearly a day to process.

この正規表現は、一致する入力の形式ばかりを念頭に置いて開発されていて、パターンに一致しない入力のことが考慮されていません。Because this regular expression was developed solely by considering the format of input to be matched, it fails to take account of input that does not match the pattern. そのため、制約のない入力が正規表現パターンにほぼ一致する場合に、パフォーマンスが大幅に低下する可能性があります。This, in turn, can allow unconstrained input that nearly matches the regular expression pattern to significantly degrade performance.

この問題を解決する方法を以下に示します。To solve this problem, you can do the following:

  • パターンを開発するときには、バックトラッキングが正規表現エンジンのパフォーマンスに与える影響を考慮に入れます。特に、制約のない入力を処理する正規表現ではこれが重要です。When developing a pattern, you should consider how backtracking might affect the performance of the regular expression engine, particularly if your regular expression is designed to process unconstrained input. 詳細については、このトピックの「バックトラッキングを管理する」を参照してください。For more information, see the Take Charge of Backtracking section.

  • 有効な入力だけでなく、無効な入力と有効に近い入力も使用して、正規表現を徹底的にテストします。Thoroughly test your regular expression using invalid and near-valid input as well as valid input. 特定の正規表現に対する入力をランダムに生成するには、Microsoft Research の正規表現調査ツール Rex を使用できます。To generate input for a particular regular expression randomly, you can use Rex, which is a regular expression exploration tool from Microsoft Research.

オブジェクトのインスタンス化を適切に処理するHandle object instantiation appropriately

.NET の正規表現オブジェクト モデルの中核となるのは、正規表現エンジンを表す System.Text.RegularExpressions.Regex クラスです。At the heart of .NET’s regular expression object model is the System.Text.RegularExpressions.Regex class, which represents the regular expression engine. Regex エンジンの使用方法は、多くの場合、正規表現のパフォーマンスを左右する最大の要因になります。Often, the single greatest factor that affects regular expression performance is the way in which the Regex engine is used. 正規表現を定義するときには、正規表現エンジンと正規表現パターンを密に結合する必要があります。Defining a regular expression involves tightly coupling the regular expression engine with a regular expression pattern. この結合のプロセスは、正規表現パターンをコンストラクターに渡して Regex オブジェクトをインスタンス化する場合も、分析する文字列と共に正規表現パターンを渡して静的メソッドを呼び出す場合も、必然的にコストが高くなります。That coupling process, whether it involves instantiating a Regex object by passing its constructor a regular expression pattern or calling a static method by passing it the regular expression pattern along with the string to be analyzed, is by necessity an expensive one.

注意

解釈される正規表現とコンパイルされる正規表現を使用する場合のパフォーマンスへの暗黙的な影響に関する詳細な議論については、「Optimizing Regular Expression Performance, Part II:Taking Charge of Backtracking」(正規表現のパフォーマンスの最適化、パート II: バックトラッキングの管理) を BCL チームのブログ上で参照してください。For a more detailed discussion of the performance implications of using interpreted and compiled regular expressions, see Optimizing Regular Expression Performance, Part II: Taking Charge of Backtracking in the BCL Team blog.

正規表現エンジンを特定の正規表現パターンと結合し、そのエンジンを使用してテキストを照合するには、次のようにいくつかの方法があります。You can couple the regular expression engine with a particular regular expression pattern and then use the engine to match text in several ways:

  • パターン一致を実行する静的メソッド (Regex.Match(String, String) など) を呼び出します。You can call a static pattern-matching method, such as Regex.Match(String, String). この場合は、正規表現オブジェクトをインスタンス化する必要はありません。This does not require instantiation of a regular expression object.

  • Regex オブジェクトをインスタンス化し、解釈される正規表現のパターン一致を実行するインスタンス メソッドを呼び出します。You can instantiate a Regex object and call an instance pattern-matching method of an interpreted regular expression. これは、正規表現エンジンを正規表現パターンにバインドするための既定の方法です。This is the default method for binding the regular expression engine to a regular expression pattern. Regex オブジェクトをインスタンス化するときに、options フラグを含む Compiled 引数を指定しないと、この方法が使用されます。It results when a Regex object is instantiated without an options argument that includes the Compiled flag.

  • Regex オブジェクトをインスタンス化し、コンパイルされた正規表現のパターン一致を実行するインスタンス メソッドを呼び出します。You can instantiate a Regex object and call an instance pattern-matching method of a compiled regular expression. Regex オブジェクトをインスタンス化するときに、options フラグを含む Compiled 引数を指定した場合、正規表現オブジェクトはコンパイル済みのパターンを表します。Regular expression objects represent compiled patterns when a Regex object is instantiated with an options argument that includes the Compiled flag.

  • 特定の正規表現パターンと密に結合された専用の Regex オブジェクトを作成し、コンパイルして、スタンドアロンのアセンブリに保存します。You can create a special-purpose Regex object that is tightly coupled with a particular regular expression pattern, compile it, and save it to a standalone assembly. これを行うには、Regex.CompileToAssembly メソッドを呼び出します。You do this by calling the Regex.CompileToAssembly method.

正規表現の一致メソッドを呼び出す方法は、アプリケーションのパフォーマンスに大きな影響を与える可能性があります。The particular way in which you call regular expression matching methods can have a significant impact on your application. 以降では、アプリケーションのパフォーマンスを改善するために、静的メソッドの呼び出し、解釈される正規表現、およびコンパイルされる正規表現を使い分ける方法について説明します。The following sections discuss when to use static method calls, interpreted regular expressions, and compiled regular expressions to improve your application's performance.

重要

メソッド呼び出しの形式 (静的、解釈、コンパイル) は、メソッド呼び出しで同じ正規表現が繰り返し使用される場合や、アプリケーションで正規表現オブジェクトが多用される場合に、パフォーマンスに影響を与えます。The form of the method call (static, interpreted, compiled) affects performance if the same regular expression is used repeatedly in method calls, or if an application makes extensive use of regular expression objects.

静的正規表現Static regular expressions

静的正規表現メソッドは、正規表現オブジェクトを同じ正規表現で繰り返しインスタンス化する代わりの方法として推奨されます。Static regular expression methods are recommended as an alternative to repeatedly instantiating a regular expression object with the same regular expression. 正規表現オブジェクトで使用される正規表現パターンとは異なり、静的メソッドの呼び出しで使用されるパターンの場合は、そのオペレーション コードまたはコンパイルされた Microsoft Intermediate Language (MSIL) が正規表現エンジンによって内部にキャッシュされます。Unlike regular expression patterns used by regular expression objects, either the operation codes or the compiled Microsoft intermediate language (MSIL) from patterns used in static method calls is cached internally by the regular expression engine.

たとえば、ユーザー入力を検証するために別のメソッドを頻繁に呼び出すイベント ハンドラーがあったとします。For example, an event handler frequently calls another method to validate user input. これを反映したコードを次の例に示します。この例では、Button コントロールの Click イベントを使用して IsValidCurrency というメソッドを呼び出しています。このメソッドは、ユーザーが通貨記号に続けて 1 文字以上の 10 進数の数字を入力したかどうかを確認します。This is reflected in the following code, in which a Button control's Click event is used to call a method named IsValidCurrency, which checks whether the user has entered a currency symbol followed by at least one decimal digit.

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 メソッドのきわめて非効率的な実装を示しています。A very inefficient implementation of the IsValidCurrency method is shown in the following example. この例では、このメソッドが呼び出されるたびに Regex オブジェクトが同じパターンでインスタンス化されます。Note that each method call reinstantiates a Regex object with the same pattern. そのため、メソッドが呼び出されるたびに正規表現パターンを再コンパイルしなければならなくなります。This, in turn, means that the regular expression pattern must be recompiled each time the method is called.

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) メソッドの呼び出しに置き換える必要があります。You should replace this inefficient code with a call to the static Regex.IsMatch(String, String) method. これにより、パターン一致メソッドを呼び出すたびに Regex オブジェクトをインスタンス化する必要がなくなり、正規表現エンジンがキャッシュからコンパイル済みの正規表現を取得できるようになります。This eliminates the need to instantiate a Regex object each time you want to call a pattern-matching method, and enables the regular expression engine to retrieve a compiled version of the regular expression from its cache.

using System;
using System.Text.RegularExpressions;

public class RegexLib
{
   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 個までキャッシュされます。By default, the last 15 most recently used static regular expression patterns are cached. アプリケーションで多数の静的正規表現をキャッシュする必要がある場合は、Regex.CacheSize プロパティを設定してキャッシュのサイズを調整できます。For applications that require a larger number of cached static regular expressions, the size of the cache can be adjusted by setting the Regex.CacheSize property.

この例で使用されている正規表現 \p{Sc}+\s*\d+ は、入力文字列が通貨記号と 1 文字以上の 10 進数の数字で構成されているかどうかを確認します。The regular expression \p{Sc}+\s*\d+ that is used in this example verifies that the input string consists of a currency symbol and at least one decimal digit. このパターンは、次の表に示すように定義されます。The pattern is defined as shown in the following table.

パターンPattern 説明Description
\p{Sc}+ Unicode の Symbol, Currency カテゴリの 1 個以上の文字と一致します。Match one or more characters in the Unicode Symbol, Currency category.
\s* 0 個以上の空白文字と一致します。Match zero or more white-space characters.
\d+ 1 個以上の 10 進数と一致します。Match one or more decimal digits.

解釈される正規表現とコンパイルされる正規表現Interpreted vs. compiled regular expressions

Compiled オプションを指定して正規表現エンジンにバインドされていない正規表現パターンは、解釈されます。Regular expression patterns that are not bound to the regular expression engine through the specification of the Compiled option are interpreted. 正規表現オブジェクトがインスタンス化されるとき、正規表現エンジンは、その正規表現を一連のオペレーション コードに変換します。When a regular expression object is instantiated, the regular expression engine converts the regular expression to a set of operation codes. インスタンス メソッドが呼び出されると、オペレーション コードが MSIL に変換され、JIT コンパイラによって実行されます。When an instance method is called, the operation codes are converted to MSIL and executed by the JIT compiler. 同様に、静的正規表現メソッドが呼び出され、その正規表現がキャッシュに見つからない場合、正規表現エンジンは、その正規表現を一連のオペレーション コードに変換し、キャッシュに格納します。Similarly, when a static regular expression method is called and the regular expression cannot be found in the cache, the regular expression engine converts the regular expression to a set of operation codes and stores them in the cache. それらのオペレーション コードは、その後、JIT コンパイラで実行できるように MSIL に変換されます。It then converts these operation codes to MSIL so that the JIT compiler can execute them. 解釈される正規表現では、実行時間が長くなる代わりに、スタートアップ時間が短縮されます。Interpreted regular expressions reduce startup time at the cost of slower execution time. このため、正規表現を使用するメソッドの呼び出し回数が少ない場合、または正確な回数はわからなくても少ないと予想される場合に適しています。Because of this, they are best used when the regular expression is used in a small number of method calls, or if the exact number of calls to regular expression methods is unknown but is expected to be small. メソッド呼び出しの回数が増えるにつれて、スタートアップ時間の短縮というメリットよりも、実行速度の低下というデメリットの方が大きくなります。As the number of method calls increases, the performance gain from reduced startup time is outstripped by the slower execution speed.

Compiled オプションを指定して正規表現エンジンにバインドされた正規表現パターンは、コンパイルされます。Regular expression patterns that are bound to the regular expression engine through the specification of the Compiled option are compiled. つまり、正規表現オブジェクトがインスタンス化されるとき、または静的正規表現メソッドが呼び出され、その正規表現がキャッシュに見つからないとき、正規表現エンジンは、その正規表現を一連の中間的なオペレーション コードに変換し、さらに MSIL に変換します。This means that, when a regular expression object is instantiated, or when a static regular expression method is called and the regular expression cannot be found in the cache, the regular expression engine converts the regular expression to an intermediary set of operation codes, which it then converts to MSIL. メソッドが呼び出されると、その MSIL が JIT コンパイラによって実行されます。When a method is called, the JIT compiler executes the MSIL. コンパイルされる正規表現では、解釈される正規表現とは対照的に、スタートアップ時間が長くなる一方で、個々のパターン一致メソッドの実行時間は短くなります。In contrast to interpreted regular expressions, compiled regular expressions increase startup time but execute individual pattern-matching methods faster. 結果として、正規表現のコンパイルによって得られるパフォーマンス上のメリットは、正規表現メソッドが呼び出される回数に比例して大きくなります。As a result, the performance benefit that results from compiling the regular expression increases in proportion to the number of regular expression methods called.

要約すると、特定の正規表現について正規表現メソッドを呼び出す回数が比較的少ない場合は、解釈される正規表現を使用することをお勧めします。To summarize, we recommend that you use interpreted regular expressions when you call regular expression methods with a specific regular expression relatively infrequently. 特定の正規表現について正規表現メソッドを呼び出す回数が比較的多い場合は、コンパイルされる正規表現を使用することをお勧めします。You should use compiled regular expressions when you call regular expression methods with a specific regular expression relatively frequently. 解釈される正規表現で実行速度の低下がスタートアップ時間の短縮を上回るしきい値や、コンパイルされる正規表現でスタートアップ時間の増加が実行速度の向上を上回るしきい値については、正確な値を特定するのは困難です。The exact threshold at which the slower execution speeds of interpreted regular expressions outweigh gains from their reduced startup time, or the threshold at which the slower startup times of compiled regular expressions outweigh gains from their faster execution speeds, is difficult to determine. これは、正規表現の複雑さや、処理される個々のデータなど、さまざまな要因に依存します。It depends on a variety of factors, including the complexity of the regular expression and the specific data that it processes. 特定のアプリケーションのシナリオにおいて、解釈される正規表現とコンパイルされる正規表現のどちらで最適なパフォーマンスが得られるかを特定するには、Stopwatch クラスを使用して実行時間を比較します。To determine whether interpreted or compiled regular expressions offer the best performance for your particular application scenario, you can use the Stopwatch class to compare their execution times.

次の例では、Theodore Dreiser の『The Financier』の最初の 10 個の文を読み取る場合とすべての文を読み取る場合について、コンパイルされる正規表現と解釈される正規表現のパフォーマンスを比較しています。The following example compares the performance of compiled and interpreted regular expressions when reading the first ten sentences and when reading all the sentences in the text of Theodore Dreiser's The Financier. 出力を見るとわかるように、正規表現の一致メソッドの呼び出しが 10 回だけの場合は、解釈される正規表現の方がコンパイルされる正規表現よりパフォーマンスが高くなります。As the output from the example shows, when only ten calls are made to regular expression matching methods, an interpreted regular expression offers better performance than a compiled regular expression. しかし、多数の呼び出し (この場合は 13,000 回以上) が行われる場合は、コンパイルされる正規表現の方がパフォーマンスが高くなります。However, a compiled regular expression offers better performance when a large number of calls (in this case, over 13,000) are made.

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

public class Example
{
   public static void Main()
   {
      string pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
      Stopwatch sw;
      Match match;
      int ctr;

      StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");
      string input = inFile.ReadToEnd();
      inFile.Close();
      
      // Read first ten sentences with interpreted regex.
      Console.WriteLine("10 Sentences with Interpreted Regex:");
      sw = Stopwatch.StartNew();
      Regex int10 = new Regex(pattern, RegexOptions.Singleline);
      match = int10.Match(input);
      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("   {0} matches in {1}", ctr, 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(input);
      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("   {0} matches in {1}", ctr, sw.Elapsed);
      
      // Read all sentences with interpreted regex.
      Console.WriteLine("All Sentences with Interpreted Regex:");
      sw = Stopwatch.StartNew();
      Regex intAll = new Regex(pattern, RegexOptions.Singleline);
      match = intAll.Match(input);
      int matches = 0;
      while (match.Success) {
         matches++;
         // Do nothing with the match except get the next match.
         match = match.NextMatch();
      }
      sw.Stop();
      Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed);
      
      // Read all sentences with compiled regex.
      Console.WriteLine("All Sentences with Compiled Regex:");
      sw = Stopwatch.StartNew();
      Regex compAll = new Regex(pattern, 
                      RegexOptions.Singleline | RegexOptions.Compiled);
      match = compAll.Match(input);
      matches = 0;
      while (match.Success) {
         matches++;
         // Do nothing with the match except get the next match.
         match = match.NextMatch();
      }
      sw.Stop();
      Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed);      
   }
}
// The example displays the following output:
//       10 Sentences with Interpreted Regex:
//          10 matches in 00:00:00.0047491
//       10 Sentences with Compiled Regex:
//          10 matches in 00:00:00.0141872
//       All Sentences with Interpreted Regex:
//          13,443 matches in 00:00:01.1929928
//       All Sentences with Compiled Regex:
//          13,443 matches in 00:00:00.7635869
//       
//       >compare1
//       10 Sentences with Interpreted Regex:
//          10 matches in 00:00:00.0046914
//       10 Sentences with Compiled Regex:
//          10 matches in 00:00:00.0143727
//       All Sentences with Interpreted Regex:
//          13,443 matches in 00:00:01.1514100
//       All Sentences with Compiled Regex:
//          13,443 matches in 00:00:00.7432921
Imports System.Diagnostics
Imports System.IO
Imports System.Text.RegularExpressions

Module Example
   Public Sub Main()
      Dim pattern As String = "\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]"
      Dim sw As Stopwatch
      Dim match As Match
      Dim ctr As Integer

      Dim inFile As New StreamReader(".\Dreiser_TheFinancier.txt")
      Dim input As String = inFile.ReadToEnd()
      inFile.Close()
      
      ' Read first ten sentences with interpreted regex.
      Console.WriteLine("10 Sentences with Interpreted Regex:")
      sw = Stopwatch.StartNew()
      Dim int10 As New Regex(pattern, RegexOptions.SingleLine)
      match = int10.Match(input)
      For ctr = 0 To 9
         If match.Success Then
            ' Do nothing with the match except get the next match.
            match = match.NextMatch()
         Else
            Exit For
         End If
      Next
      sw.Stop()
      Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed)
      
      ' Read first ten sentences with compiled regex.
      Console.WriteLine("10 Sentences with Compiled Regex:")
      sw = Stopwatch.StartNew()
      Dim comp10 As New Regex(pattern, 
                   RegexOptions.SingleLine Or RegexOptions.Compiled)
      match = comp10.Match(input)
      For ctr = 0 To 9
         If match.Success Then
            ' Do nothing with the match except get the next match.
            match = match.NextMatch()
         Else
            Exit For
         End If
      Next
      sw.Stop()
      Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed)
      
      ' Read all sentences with interpreted regex.
      Console.WriteLine("All Sentences with Interpreted Regex:")
      sw = Stopwatch.StartNew()
      Dim intAll As New Regex(pattern, RegexOptions.SingleLine)
      match = intAll.Match(input)
      Dim matches As Integer = 0
      Do While match.Success
         matches += 1
         ' Do nothing with the match except get the next match.
         match = match.NextMatch()
      Loop
      sw.Stop()
      Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed)
      
      ' Read all sentences with compiled regex.
      Console.WriteLine("All Sentences with Compiled Regex:")
      sw = Stopwatch.StartNew()
      Dim compAll As New Regex(pattern, 
                     RegexOptions.SingleLine Or RegexOptions.Compiled)
      match = compAll.Match(input)
      matches = 0
      Do While match.Success
         matches += 1
         ' Do nothing with the match except get the next match.
         match = match.NextMatch()
      Loop
      sw.Stop()
      Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed)      
   End Sub
End Module
' The example displays output like the following:
'       10 Sentences with Interpreted Regex:
'          10 matches in 00:00:00.0047491
'       10 Sentences with Compiled Regex:
'          10 matches in 00:00:00.0141872
'       All Sentences with Interpreted Regex:
'          13,443 matches in 00:00:01.1929928
'       All Sentences with Compiled Regex:
'          13,443 matches in 00:00:00.7635869
'       
'       >compare1
'       10 Sentences with Interpreted Regex:
'          10 matches in 00:00:00.0046914
'       10 Sentences with Compiled Regex:
'          10 matches in 00:00:00.0143727
'       All Sentences with Interpreted Regex:
'          13,443 matches in 00:00:01.1514100
'       All Sentences with Compiled Regex:
'          13,443 matches in 00:00:00.7432921

この例で使用する正規表現パターン \b(\w+((\r?\n)|,?\s))*\w+[.?:;!] は、次の表に示すように定義されています。The regular expression pattern used in the example, \b(\w+((\r?\n)|,?\s))*\w+[.?:;!], is defined as shown in the following table.

パターンPattern 説明Description
\b ワード境界から照合を開始します。Begin the match at a word boundary.
\w+ 1 つ以上の単語文字に一致します。Match one or more word characters.
(\r?\n)|,?\s) 0 ~ 1 個の復帰とそれに続く改行文字、または 0 ~ 1 個のコンマとそれに続く空白文字に一致します。Match either zero or one carriage return followed by a newline character, or zero or one comma followed by a white-space character.
(\w+((\r?\n)|,?\s))* 1 個以上の単語文字の後に 0 ~ 1 個の復帰と改行文字または 0 ~ 1 個のコンマと空白文字が続くパターンの 0 回以上の出現に一致します。Match zero or more occurrences of one or more word characters that are followed either by zero or one carriage return and a newline character, or by zero or one comma followed by a white-space character.
\w+ 1 つ以上の単語文字に一致します。Match one or more word characters.
[.?:;!] ピリオド、疑問符、コロン、セミコロン、または感嘆符に一致します。Match a period, question mark, colon, semicolon, or exclamation point.

正規表現: アセンブリにコンパイル済みRegular expressions: Compiled to an assembly

.NET では、コンパイル済みの正規表現を含むアセンブリを作成することもできます。.NET also enables you to create an assembly that contains compiled regular expressions. これにより、パフォーマンスの低下を招く正規表現のコンパイルを、実行時ではなくデザイン時に行うことができます。This moves the performance hit of regular expression compilation from run time to design time. ただし、いくつかの追加の作業も伴います。正規表現を事前に定義して、アセンブリにコンパイルする必要があります。However, it also involves some additional work: You must define the regular expressions in advance and compile them to an assembly. これにより、アセンブリの正規表現を使用するソース コードをコンパイルするときに、コンパイラがそのアセンブリを参照できるようになります。The compiler can then reference this assembly when compiling source code that uses the assembly’s regular expressions. アセンブリに含まれるコンパイル済みの各正規表現は、Regex の派生クラスによって表されます。Each compiled regular expression in the assembly is represented by a class that derives from Regex.

正規表現をアセンブリにコンパイルするには、Regex.CompileToAssembly(RegexCompilationInfo[], AssemblyName) メソッドを呼び出して、コンパイルする正規表現を表す RegexCompilationInfo オブジェクトの配列と、作成するアセンブリに関する情報を含む AssemblyName オブジェクトを渡します。To compile regular expressions to an assembly, you call the Regex.CompileToAssembly(RegexCompilationInfo[], AssemblyName) method and pass it an array of RegexCompilationInfo objects that represent the regular expressions to be compiled, and an AssemblyName object that contains information about the assembly to be created.

次のような状況では、正規表現をアセンブリにコンパイルすることをお勧めします。We recommend that you compile regular expressions to an assembly in the following situations:

  • コンポーネント開発者が、再利用できる正規表現のライブラリを作成する場合。If you are a component developer who wants to create a library of reusable regular expressions.

  • 正規表現のパターン一致メソッドが呼び出される回数を特定できない場合 (1 ~ 2 回から数千~数万回の範囲)。If you expect your regular expression's pattern-matching methods to be called an indeterminate number of times -- anywhere from once or twice to thousands or tens of thousands of times. 別個のアセンブリにコンパイルされた正規表現では、コンパイルされる正規表現や解釈される正規表現とは違って、メソッド呼び出しの回数に関係なく一貫したパフォーマンスが得られます。Unlike compiled or interpreted regular expressions, regular expressions that are compiled to separate assemblies offer performance that is consistent regardless of the number of method calls.

コンパイル済みの正規表現を使用してパフォーマンスを最適化する場合は、アセンブリの作成、正規表現エンジンの読み込み、およびそのパターン一致メソッドの実行にリフレクションを使用しないようにする必要があります。If you are using compiled regular expressions to optimize performance, you should not use reflection to create the assembly, load the regular expression engine, and execute its pattern-matching methods. そのためには、正規表現パターンを動的に構築しないこと、およびパターン一致オプション (大文字と小文字を区別しないパターン一致など) をアセンブリの作成時に指定することが要求されます。This requires that you avoid building regular expression patterns dynamically, and that you specify any pattern-matching options (such as case-insensitive pattern matching) at the time the assembly is created. さらに、アセンブリを作成するコードを、正規表現を使用するコードから分離する必要もあります。It also requires that you separate the code that creates the assembly from the code that uses the regular expression.

次の例は、コンパイル済みの正規表現を含むアセンブリを作成する方法を示しています。The following example shows how to create an assembly that contains a compiled regular expression. この例では、RegexLib.dll という 1 つの正規表現クラスを含む SentencePattern という名前のアセンブリを作成しています。このクラスには、「解釈される正規表現とコンパイルされる正規表現」セクションで使用した、文に一致する正規表現パターンが含まれています。It creates an assembly named RegexLib.dll with a single regular expression class, SentencePattern, that contains the sentence-matching regular expression pattern used in the Interpreted vs. Compiled Regular Expressions section.

using System;
using System.Reflection;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      RegexCompilationInfo SentencePattern =
                           new RegexCompilationInfo(@"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
                                                    RegexOptions.Multiline,
                                                    "SentencePattern",
                                                    "Utilities.RegularExpressions",
                                                    true);
      RegexCompilationInfo[] regexes = { SentencePattern };
      AssemblyName assemName = new AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null");
      Regex.CompileToAssembly(regexes, assemName);
   }
}
Imports System.Reflection
Imports System.Text.RegularExpressions

Module Example
   Public Sub Main()
      Dim SentencePattern As New RegexCompilationInfo("\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
                                                      RegexOptions.Multiline,
                                                      "SentencePattern",
                                                      "Utilities.RegularExpressions",
                                                      True)
      Dim regexes() As RegexCompilationInfo = {SentencePattern}
      Dim assemName As New AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null")
      Regex.CompileToAssembly(regexes, assemName)
   End Sub
End Module

この例を実行可能ファイルにコンパイルして実行すると、RegexLib.dll という名前のアセンブリが作成されます。When the example is compiled to an executable and run, it creates an assembly named RegexLib.dll. 正規表現は、Utilities.RegularExpressions.SentencePattern から派生する Regex という名前のクラスによって表されます。The regular expression is represented by a class named Utilities.RegularExpressions.SentencePattern that is derived from Regex. 次の例では、このコンパイル済みの正規表現を使用して、Theodore Dreiser の『The Financier』のテキストから文を抽出しています。The following example then uses the compiled regular expression to extract the sentences from the text of Theodore Dreiser's The Financier.

using System;
using System.IO;
using System.Text.RegularExpressions;
using Utilities.RegularExpressions;

public class Example
{
   public static void Main()
   {
      SentencePattern pattern = new SentencePattern();
      StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");
      string input = inFile.ReadToEnd();
      inFile.Close();
      
      MatchCollection matches = pattern.Matches(input);
      Console.WriteLine("Found {0:N0} sentences.", matches.Count);      
   }
}
// The example displays the following output:
//      Found 13,443 sentences.
Imports System.IO
Imports System.Text.RegularExpressions
Imports Utilities.RegularExpressions

Module Example
   Public Sub Main()
      Dim pattern As New SentencePattern()
      Dim inFile As New StreamReader(".\Dreiser_TheFinancier.txt")
      Dim input As String = inFile.ReadToEnd()
      inFile.Close()
      
      Dim matches As MatchCollection = pattern.Matches(input)
      Console.WriteLine("Found {0:N0} sentences.", matches.Count)      
   End Sub
End Module
' The example displays the following output:
'      Found 13,443 sentences.

バックトラッキングを管理するTake charge of backtracking

通常、正規表現エンジンは入力文字列内を直線的に進んで、入力文字列を正規表現パターンと比較します。Ordinarily, the regular expression engine uses linear progression to move through an input string and compare it to a regular expression pattern. しかし、正規表現パターン内で不定量指定子 (*+? など) が使用されていると、パターン全体に対する一致を検索するために、それまでに見つかった部分的な一致を放棄して、以前に保存した状態に戻る場合があります。However, when indeterminate quantifiers such as *, +, and ? are used in a regular expression pattern, the regular expression engine may give up a portion of successful partial matches and return to a previously saved state in order to search for a successful match for the entire pattern. このプロセスをバックトラッキングと呼びます。This process is known as backtracking.

注意

バックトラッキングの詳細については、「正規表現の動作の詳細」と「バックトラッキング」を参照してください。For more information on backtracking, see Details of Regular Expression Behavior and Backtracking. バックトラッキングに関する詳細な議論については、「Optimizing Regular Expression Performance, Part II:Taking Charge of Backtracking」(正規表現のパフォーマンスの最適化、パート II: バックトラッキングの管理) を BCL チームのブログ上で参照してください。For a detailed discussion of backtracking, see Optimizing Regular Expression Performance, Part II: Taking Charge of Backtracking in the BCL Team blog.

バックトラッキングのサポートにより、正規表現はより強力かつ柔軟になります。Support for backtracking gives regular expressions power and flexibility. 同時に、正規表現エンジンの動作を正規表現の開発者が制御することにもなります。It also places the responsibility for controlling the operation of the regular expression engine in the hands of regular expression developers. この責任を認識していない開発者によるバックトラッキングの誤用や過度なバックトラッキングへの依存が、多くの場合、正規表現のパフォーマンスを低下させる最大の要因になっています。Because developers are often not aware of this responsibility, their misuse of backtracking or reliance on excessive backtracking often plays the most significant role in degrading regular expression performance. 最悪のシナリオでは、入力文字列が 1 文字増えるたびに実行時間が倍増することもあります。In a worst-case scenario, execution time can double for each additional character in the input string. 実際、バックトラッキングを過度に使用すると、入力が正規表現パターンにほぼ一致する場合に、プログラム的に無限ループと同等の状態に陥りやすくなります。そのような状態では、正規表現エンジンによる比較的短い入力文字列の処理に何時間も何日もかかることがあります。In fact, by using backtracking excessively, it is easy to create the programmatic equivalent of an endless loop if input nearly matches the regular expression pattern; the regular expression engine may take hours or even days to process a relatively short input string.

照合に必要でないにもかかわらずバックトラッキングを使用した代償として、アプリケーションのパフォーマンスが低下することもよくあります。Often, applications pay a performance penalty for using backtracking despite the fact that backtracking is not essential for a match. 例として、\b\p{Lu}\w*\b という正規表現について見てみましょう。この正規表現は、次の表に示すように、大文字で始まるすべての単語に一致します。For example, the regular expression \b\p{Lu}\w*\b matches all words that begin with an uppercase character, as the following table shows.

パターンPattern 説明Description
\b ワード境界から照合を開始します。Begin the match at a word boundary.
\p{Lu} 大文字に一致します。Match an uppercase character.
\w* 0 個以上の単語に使用される文字に一致します。Match zero or more word characters.
\b ワード境界で照合を終了します。End the match at a word boundary.

ワード境界は単語文字と同じではなく、単語文字のサブセットでもないため、正規表現エンジンが単語文字の照合中にワード境界を越える可能性はありません。Because a word boundary is not the same as, or a subset of, a word character, there is no possibility that the regular expression engine will cross a word boundary when matching word characters. したがって、この正規表現では、バックトラッキングが照合の全体的な成功に寄与することはありません。バックトラッキングを使用すると、正規表現エンジンが単語文字の照合に成功するたびに状態を保存しなければならなくなるため、パフォーマンスを低下させるだけです。This means that for this regular expression, backtracking can never contribute to the overall success of any match -- it can only degrade performance, because the regular expression engine is forced to save its state for each successful preliminary match of a word character.

バックトラッキングが不要であることがわかった場合は、(?>subexpression) 言語要素を使用して無効にすることができます。If you determine that backtracking is not necessary, you can disable it by using the (?>subexpression) language element. 次の例では、2 つの正規表現を使用して入力文字列を解析しています。The following example parses an input string by using two regular expressions. 1 つはバックトラッキングに依存する \b\p{Lu}\w*\bThe first, \b\p{Lu}\w*\b, relies on backtracking. もう 1 つはバックトラッキングを無効にする \b\p{Lu}(?>\w*)\b です。The second, \b\p{Lu}(?>\w*)\b, disables backtracking. 出力を見るとわかるように、どちらも結果は同じになります。As the output from the example shows, they both produce the same result.

using System;
using System.Text.RegularExpressions;

public class Example
{
   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

正規表現パターンと入力テキストの照合にバックトラッキングが欠かせない場合もよくあります。In many cases, backtracking is essential for matching a regular expression pattern to input text. ただし、過度なバックトラッキングが発生すると、極端にパフォーマンスが低下して、アプリケーションが応答しなくなったように見えることがあります。However, excessive backtracking can severely degrade performance and create the impression that an application has stopped responding. この状況が発生するのは、たとえば、量指定子が入れ子になっていて、外側の部分式に一致するテキストが内側の部分式に一致するテキストのサブセットになっている場合です。In particular, this happens when quantifiers are nested and the text that matches the outer subexpression is a subset of the text that matches the inner subexpression.

警告

過度なバックトラッキングの回避に加えて、タイムアウト機能を使用して、過度なバックトラッキングによって、正規表現のパフォーマンスが極端に低下するのを防ぐ必要があります。In addition to avoiding excessive backtracking, you should use the timeout feature to ensure that excessive backtracking does not severely degrade regular expression performance. 詳しくは、「タイムアウト値を使用する」をご覧ください。For more information, see the Use Time-out Values section.

例として、部品番号に一致するように作られた ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ という正規表現パターンについて見てみましょう。この部品番号は、少なくとも 1 文字の英数字で構成されます。For example, the regular expression pattern ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ is intended to match a part number that consists of at least one alphanumeric character. 追加の文字では、英数字、ハイフン、アンダースコア、およびピリオドが許容されますが、最後の文字は英数字でなければなりません。Any additional characters can consist of an alphanumeric character, a hyphen, an underscore, or a period, though the last character must be alphanumeric. 部品番号の終わりはドル記号で示されます。A dollar sign terminates the part number. この正規表現パターンは、量指定子が入れ子になっているうえに、部分式 [0-9A-Z] が部分式 [-.\w]* のサブセットであるため、パフォーマンスが極端に低下する可能性があります。In some cases, this regular expression pattern can exhibit extremely poor performance because quantifiers are nested, and because the subexpression [0-9A-Z] is a subset of the subexpression [-.\w]*.

このような場合に正規表現のパフォーマンスを最適化するには、入れ子になった量指定子を削除して、外側の部分式をゼロ幅の先読みアサーションまたは後読みアサーションに置き換えます。In these cases, you can optimize regular expression performance by removing the nested quantifiers and replacing the outer subexpression with a zero-width lookahead or lookbehind assertion. 先読みアサーションと後読みアサーションはアンカーなので、入力文字列内でポインターを移動させることなく、先読みまたは後読みによって、指定された条件が満たされているかどうかを確認します。Lookahead and lookbehind assertions are anchors; they do not move the pointer in the input string, but instead look ahead or behind to check whether a specified condition is met. たとえば、この部品番号の正規表現は ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$ として書き直すことができます。For example, the part number regular expression can be rewritten as ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$. この正規表現パターンは、次の表に示すように定義されます。This regular expression pattern is defined as shown in the following table.

パターンPattern 説明Description
^ 入力文字列の先頭から照合を開始します。Begin the match at the beginning of the input string.
[0-9A-Z] 英数字 1 文字に一致します。Match an alphanumeric character. これは、部品番号に最低限必要な文字です。The part number must consist of at least this character.
[-.\w]* 任意の単語文字、ハイフン、またはピリオドの 0 回以上の出現に一致します。Match zero or more occurrences of any word character, hyphen, or period.
\$ ドル記号に一致します。Match a dollar sign.
(?<=[0-9A-Z]) 末尾のドル記号を先読みし、前の文字が英数字であることを確認します。Look ahead of the ending dollar sign to ensure that the previous character is alphanumeric.
$ 入力文字列の末尾で照合を終了します。End the match at the end of the input string.

次の例では、この正規表現を使用して、部品番号を含む配列を照合しています。The following example illustrates the use of this regular expression to match an array containing possible part numbers.

using System;
using System.Text.RegularExpressions;

public class Example
{
   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 の正規表現言語には、入れ子になった量指定子を取り除くために使用できる次の言語要素が含まれています。The regular expression language in .NET includes the following language elements that you can use to eliminate nested quantifiers. 詳細については、「 グループ化構成体」を参照してください。For more information, see Grouping Constructs.

言語要素Language element 説明Description
(?= subexpression )(?= subexpression ) ゼロ幅の肯定先読みです。Zero-width positive lookahead. 現在の位置からの先読みにより、subexpression が入力文字列に一致するかどうかを確認します。Look ahead of the current position to determine whether subexpression matches the input string.
(?! subexpression )(?! subexpression ) ゼロ幅の否定先読みです。Zero-width negative lookahead. 現在の位置からの先読みにより、subexpression が入力文字列に一致しないかどうかを確認します。Look ahead of the current position to determine whether subexpression does not match the input string.
(?<= subexpression )(?<= subexpression ) ゼロ幅の肯定後読みです。Zero-width positive lookbehind. 現在の位置からの後読みにより、subexpression が入力文字列に一致するかどうかを確認します。Look behind the current position to determine whether subexpression matches the input string.
(?<! subexpression )(?<! subexpression ) ゼロ幅の否定後読みです。Zero-width negative lookbehind. 現在の位置からの後読みにより、subexpression が入力文字列に一致しないかどうかを確認します。Look behind the current position to determine whether subexpression does not match the input string.

タイムアウト値を使用するUse time-out values

正規表現が正規表現パターンにほぼ一致する入力を処理する場合は、パフォーマンスに大きな影響を与える過度なバックトラッキングに依存することがよくあります。If your regular expressions processes input that nearly matches the regular expression pattern, it can often rely on excessive backtracking, which impacts its performance significantly. 慎重にバックトラッキングの使用を検討し、ほぼ一致する入力に対して正規表現をテストすることに加えて、過度なバックトラッキングが発生した場合にその影響を確実に最小限に抑えるために、必ずタイムアウト値を設定する必要があります。In addition to carefully considering your use of backtracking and testing the regular expression against near-matching input, you should always set a time-out value to ensure that the impact of excessive backtracking, if it occurs, is minimized.

正規表現のタイムアウト間隔は、タイムアウトする前に正規表現エンジンが単一の一致を検索する期間を定義します。既定のタイムアウト間隔は Regex.InfiniteMatchTimeout で、正規表現がタイムアウトしないことを意味します。次のようにこの値をオーバーライドし、タイムアウト間隔を定義できます。The regular expression time-out interval defines the period of time that the regular expression engine will look for a single match before it times out. The default time-out interval is Regex.InfiniteMatchTimeout, which means that the regular expression will not time out. You can override this value and define a time-out interval as follows:

タイムアウト間隔を定義していて、その間隔の終了時に一致が見つからない場合、正規表現メソッドは RegexMatchTimeoutException 例外をスローします。If you have defined a time-out interval and a match is not found at the end of that interval, the regular expression method throws a RegexMatchTimeoutException exception. 例外ハンドラーで、タイムアウト間隔を延長して一致を再試行する、一致操作を破棄して一致なしと見なす、または一致操作を破棄して今後の分析のために例外情報を記録することを選択できます。In your exception handler, you can choose to retry the match with a longer time-out interval, abandon the match attempt and assume that there is no match, or abandon the match attempt and log the exception information for future analysis.

次の例では、テキスト ドキュメントの単語の数と 1 つの単語に含まれる平均文字数を計算するために、タイムアウト間隔が 350 ミリ秒の正規表現をインスタンス化する GetWordData メソッドを定義します。The following example defines a GetWordData method that instantiates a regular expression with a time-out interval of 350 milliseconds to calculate the number of words and average number of characters in a word in a text document. 一致操作がタイムアウトした場合、タイムアウト間隔は 350 ミリ秒ずつ延長され、Regex オブジェクトが再インスタンス化されます。If the matching operation times out, the time-out interval is increased by 350 milliseconds and the Regex object is re-instantiated. 新しいタイムアウト間隔が 1 秒を超える場合、メソッドは呼び出し元に例外を再スローします。If the new time-out interval exceeds 1 second, the method re-throws the exception to the caller.

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

public class Example
{
   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:               {0:N0}", info.Item1);
         Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2); 
      }
      catch (IOException e) {
         Console.WriteLine("IOException reading file '{0}'", title);
         Console.WriteLine(e.Message);
      }
      catch (RegexMatchTimeoutException e) {
         Console.WriteLine("The operation timed out after {0:N0} milliseconds", 
                           e.MatchTimeout.TotalMilliseconds);
      }
   }
}

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

必要なときにのみキャプチャするCapture only when necessary

.NET の正規表現では、数多くのグループ化構成体がサポートされています。これらを使用すると、正規表現パターンを 1 つ以上の部分式にグループ化することができます。Regular expressions in .NET support a number of grouping constructs, which let you group a regular expression pattern into one or more subexpressions. .NET の正規表現言語で最もよく使用されるグループ化構成体は、番号付きのキャプチャ グループを定義する (subexpression) と、名前付きのキャプチャ グループを定義する (?<name>subexpression) です。The most commonly used grouping constructs in .NET regular expression language are (subexpression), which defines a numbered capturing group, and (?<name>subexpression), which defines a named capturing group. グループ化構成体は、前方参照を作成したり、量指定子を適用する部分式を定義したりするのに欠かせません。Grouping constructs are essential for creating backreferences and for defining a subexpression to which a quantifier is applied.

しかし、これらの言語要素の使用にはコストが伴います。However, the use of these language elements has a cost. まず、GroupCollection プロパティから返される Match.Groups オブジェクトに、最新の名前のないキャプチャまたは名前付きキャプチャが設定されます。また、1 つのグループ化構成体によって入力文字列の複数の部分文字列がキャプチャされた場合は、特定のキャプチャ グループの CaptureCollection プロパティから返される Group.Captures オブジェクトに複数の Capture オブジェクトが設定されます。They cause the GroupCollection object returned by the Match.Groups property to be populated with the most recent unnamed or named captures, and if a single grouping construct has captured multiple substrings in the input string, they also populate the CaptureCollection object returned by the Group.Captures property of a particular capturing group with multiple Capture objects.

正規表現内で量指定子を適用できるようにするためだけにグループ化構成体が使用されていて、それらの部分式によってキャプチャされたグループがその後に使用されていない場合もよくあります。Often, grouping constructs are used in a regular expression only so that quantifiers can be applied to them, and the groups captured by these subexpressions are not subsequently used. 例として、文全体をキャプチャするために作られた正規表現 \b(\w+[;,]?\s?)+[.?!] について見てみましょう。For example, the regular expression \b(\w+[;,]?\s?)+[.?!] is designed to capture an entire sentence. 次の表は、この正規表現パターン内の言語要素と、Match オブジェクトの Match.Groups コレクションおよび Group.Captures コレクションに対する影響を示しています。The following table describes the language elements in this regular expression pattern and their effect on the Match object's Match.Groups and Group.Captures collections.

パターンPattern 説明Description
\b ワード境界から照合を開始します。Begin the match at a word boundary.
\w+ 1 つ以上の単語文字に一致します。Match one or more word characters.
[;,]? 0 個または 1 個のコンマまたはセミコロンに一致します。Match zero or one comma or semicolon.
\s? 0 個または 1 個の空白文字と一致します。Match zero or one white-space character.
(\w+[;,]?\s?)+ 1 個以上の単語文字の後に省略可能なコンマまたはセミコロンと省略可能な空白文字が続くパターンの 1 回以上の出現に一致します。Match one or more occurrences of one or more word characters followed by an optional comma or semicolon followed by an optional white-space character. これにより、最初のキャプチャ グループが定義されます。このグループは、複数の単語文字の組み合わせ (つまり単語) の後に省略可能な区切り記号が続くパターンが、正規表現エンジンが文の終わりに到達するまで繰り返されるようにするために必要です。This defines the first capturing group, which is necessary so that the combination of multiple word characters (that is, a word) followed by an optional punctuation symbol will be repeated until the regular expression engine reaches the end of a sentence.
[.?!] ピリオド、疑問符、または感嘆符に一致します。Match a period, question mark, or exclamation point.

次の例に示すように、一致が見つかると、GroupCollection オブジェクトと CaptureCollection オブジェクトの両方に一致からのキャプチャが設定されます。As the following example shows, when a match is found, both the GroupCollection and CaptureCollection objects are populated with captures from the match. ここでは、(\w+[;,]?\s?) 量指定子を適用できるようにするためにキャプチャ グループ + が使用されているため、正規表現パターンが文の各単語に一致します。In this case, the capturing group (\w+[;,]?\s?) exists so that the + quantifier can be applied to it, which enables the regular expression pattern to match each word in a sentence. そうでない場合は、文の最後の単語に一致します。Otherwise, it would match the last word in a sentence.

using System;
using System.Text.RegularExpressions;

public class Example
{
   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: '{0}' at index {1}.", 
                           match.Value, match.Index);
         int grpCtr = 0;
         foreach (Group grp in match.Groups) {
            Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                              grpCtr, grp.Value, grp.Index);
            int capCtr = 0;
            foreach (Capture cap in grp.Captures) {
               Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                 capCtr, cap.Value, 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.

量指定子を適用するためだけに部分式を使用していて、キャプチャされたテキストは特に必要ないという場合は、グループ キャプチャを無効にする必要があります。When you use subexpressions only to apply quantifiers to them, and you are not interested in the captured text, you should disable group captures. たとえば、(?:subexpression) 言語要素をグループに適用すると、そのグループでは、一致した部分文字列がキャプチャされなくなります。For example, the (?:subexpression) language element prevents the group to which it applies from capturing matched substrings. 次の例では、前の例の正規表現パターンが \b(?:\w+[;,]?\s?)+[.?!] に変更されています。In the following example, the regular expression pattern from the previous example is changed to \b(?:\w+[;,]?\s?)+[.?!]. 出力を見るとわかるように、これにより、GroupCollection コレクションと CaptureCollection コレクションが設定されなくなります。As the output shows, it prevents the regular expression engine from populating the GroupCollection and CaptureCollection collections.

using System;
using System.Text.RegularExpressions;

public class Example
{
   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: '{0}' at index {1}.", 
                           match.Value, match.Index);
         int grpCtr = 0;
         foreach (Group grp in match.Groups) {
            Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                              grpCtr, grp.Value, grp.Index);
            int capCtr = 0;
            foreach (Capture cap in grp.Captures) {
               Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                 capCtr, cap.Value, 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.

キャプチャを無効にするには次のような方法があります。You can disable captures in one of the following ways:

  • (?:subexpression) 言語要素を使用します。Use the (?:subexpression) language element. この要素をグループに適用すると、そのグループでは、一致した部分文字列がキャプチャされなくなります。This element prevents the capture of matched substrings in the group to which it applies. 入れ子になったグループによる部分文字列のキャプチャは無効になりません。It does not disable substring captures in any nested groups.

  • ExplicitCapture オプションを使用します。Use the ExplicitCapture option. これにより、正規表現パターン内の名前のないキャプチャ (暗黙的なキャプチャ) がすべて無効になります。It disables all unnamed or implicit captures in the regular expression pattern. このオプションを使用した場合は、(?<name>subexpression) 言語要素を使用して定義した名前付きグループに一致する部分文字列のみがキャプチャされます。When you use this option, only substrings that match named groups defined with the (?<name>subexpression) language element can be captured. ExplicitCapture フラグは、options クラス コンストラクターの Regex パラメーターか、options の静的な一致メソッドの Regex パラメーターに渡すことができます。The ExplicitCapture flag can be passed to the options parameter of a Regex class constructor or to the options parameter of a Regex static matching method.

  • n 言語要素の (?imnsx) オプションを使用します。Use the n option in the (?imnsx) language element. これにより、正規表現パターンでこの要素が出現する位置以降の名前のないキャプチャ (暗黙的なキャプチャ) がすべて無効になります。This option disables all unnamed or implicit captures from the point in the regular expression pattern at which the element appears. パターンの末尾に到達するか、(-n) オプションによって名前のないキャプチャ (暗黙的なキャプチャ) が有効になるまで、キャプチャは無効のままです。Captures are disabled either until the end of the pattern or until the (-n) option enables unnamed or implicit captures. 詳細については、「 その他の構成体」を参照してください。For more information, see Miscellaneous Constructs.

  • n 言語要素の (?imnsx:subexpression) オプションを使用します。Use the n option in the (?imnsx:subexpression) language element. これにより、subexpression 内の名前のないキャプチャ (暗黙的なキャプチャ) がすべて無効になります。This option disables all unnamed or implicit captures in subexpression. 入れ子になった名前のない (暗黙的な) キャプチャ グループによるキャプチャも無効になります。Captures by any unnamed or implicit nested capturing groups are disabled as well.

TitleTitle 説明Description
正規表現の動作の詳細Details of Regular Expression Behavior .NET の正規表現エンジンの実装について検討します。Examines the implementation of the regular expression engine in .NET. 正規表現の柔軟性に焦点を当てて、正規表現エンジンの効率的かつ堅牢な動作を確保するための開発者の責任について説明します。The topic focuses on the flexibility of regular expressions and explains the developer's responsibility for ensuring the efficient and robust operation of the regular expression engine.
バックトラッキングBacktracking バックトラッキングの概要と、正規表現のパフォーマンスに与える影響について説明し、バックトラッキングの代わりに使用できる言語要素について検討します。Explains what backtracking is and how it affects regular expression performance, and examines language elements that provide alternatives to backtracking.
正規表現言語 - クイック リファレンスRegular Expression Language - Quick Reference .NET の正規表現言語の言語要素について説明します。各言語要素の詳細な説明へのリンクも含まれています。Describes the elements of the regular expression language in .NET and provides links to detailed documentation for each language element.