欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

.NET正則表達(dá)式的最佳用法

 更新時(shí)間:2022年02月08日 10:32:25   作者:痕跡g  
本文詳細(xì)講解了.NET正則表達(dá)式最佳用法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

.NET 中的正則表達(dá)式引擎是一種功能強(qiáng)大而齊全的工具,它基于模式匹配(而不是比較和匹配文本)來處理文本。 在大多數(shù)情況下,它可以快速、高效地執(zhí)行模式匹配。 但在某些情況下,正則表達(dá)式引擎的速度似乎很慢。 在極端情況下,它甚至看似停止響應(yīng),因?yàn)樗鼤萌舾蓚€(gè)小時(shí)甚至若干天處理相對小的輸入。

本主題概述開發(fā)人員為了確保其正則表達(dá)式實(shí)現(xiàn)最佳性能可以采納的一些最佳做法。

考慮輸入源

通常,正則表達(dá)式可接受兩種類型的輸入:受約束的輸入或不受約束的輸入。 受約束的輸入是源自已知或可靠的源并遵循預(yù)定義格式的文本。 不受約束的輸入是源自不可靠的源(如 Web 用戶)并且可能不遵循預(yù)定義或預(yù)期格式的文本。

編寫的正則表達(dá)式模式的目的通常是匹配有效輸入。 也就是說,開發(fā)人員檢查他們要匹配的文本,然后編寫與其匹配的正則表達(dá)式模式。 然后,開發(fā)人員使用多個(gè)有效輸入項(xiàng)進(jìn)行測試,以確定此模式是否需要更正或進(jìn)一步細(xì)化。 當(dāng)模式可匹配所有假定的有效輸入時(shí),則將其聲明為生產(chǎn)就緒并且可包括在發(fā)布的應(yīng)用程序中。 這使得正則表達(dá)式模式適合匹配受約束的輸入。 但它不適合匹配不受約束的輸入。

若要匹配不受約束的輸入,正則表達(dá)式必須能夠高效處理以下三種文本:

  • 與正則表達(dá)式模式匹配的文本。

  • 與正則表達(dá)式模式不匹配的文本。

  • 與正則表達(dá)式模式大致匹配的文本。

對于為了處理受約束的輸入而編寫的正則表達(dá)式,最后一種文本類型尤其存在問題。 如果該正則表達(dá)式還依賴大量回溯,則正則表達(dá)式引擎可能會花費(fèi)大量時(shí)間(在有些情況下,需要許多個(gè)小時(shí)或許多天)來處理看似無害的文本。

例如,考慮一種很常用但很有問題的用于驗(yàn)證電子郵件地址別名的正則表達(dá)式。 編寫正則表達(dá)式 ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ 的目的是處理被視為有效的電子郵件地址,該地址包含一個(gè)字母數(shù)字字符,后跟零個(gè)或多個(gè)可為字母數(shù)字、句點(diǎn)或連字符的字符。 該正則表達(dá)式必須以字母數(shù)字字符結(jié)束。 但正如下面的示例所示,盡管此正則表達(dá)式可以輕松處理有效輸入,但在處理接近有效的輸入時(shí)性能非常低效。

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

如該示例輸出所示,正則表達(dá)式引擎處理有效電子郵件別名的時(shí)間間隔大致相同,與其長度無關(guān)。 另一方面,當(dāng)接近有效的電子郵件地址包含五個(gè)以上字符時(shí),字符串中每增加一個(gè)字符,處理時(shí)間會大約增加一倍。 這意味著,處理接近有效的 28 個(gè)字符構(gòu)成的字符串將需要一個(gè)小時(shí),處理接近有效的 33 個(gè)字符構(gòu)成的字符串將需要接近一天的時(shí)間。

由于開發(fā)此正則表達(dá)式時(shí)只考慮了要匹配的輸入的格式,因此未能考慮與模式不匹配的輸入。 這反過來會使與正則表達(dá)式模式近似匹配的不受約束輸入的性能顯著降低。

若要解決此問題,可執(zhí)行下列操作:

  • 開發(fā)模式時(shí),應(yīng)考慮回溯對正則表達(dá)式引擎的性能的影響程度,特別是當(dāng)正則表達(dá)式設(shè)計(jì)用于處理不受約束的輸入時(shí)。 有關(guān)詳細(xì)信息,請參閱控制回溯部分。

  • 使用無效輸入、接近有效的輸入以及有效輸入對正則表達(dá)式進(jìn)行完全測試。 若要為特定正則表達(dá)式隨機(jī)生成輸入,可以使用 Rex,這是 Microsoft Research 提供的正則表達(dá)式探索工具。

適當(dāng)處理對象實(shí)例化

.NET 正則表達(dá)式對象模型的核心是 xref:System.Text.RegularExpressions.Regex?displayProperty=nameWithType 類,表示正則表達(dá)式引擎。 通常,影響正則表達(dá)式性能的單個(gè)最大因素是 xref:System.Text.RegularExpressions.Regex 引擎的使用方式。 定義正則表達(dá)式需要將正則表達(dá)式引擎與正則表達(dá)式模式緊密耦合。 無論該耦合過程是需要通過向其構(gòu)造函數(shù)傳遞正則表達(dá)式模式來實(shí)例化 xref:System.Text.RegularExpressions.Regex 還是通過向其傳遞正則表達(dá)式模式和要分析的字符串來調(diào)用靜態(tài)方法,都必然會消耗大量資源。

可將正則表達(dá)式引擎與特定正則表達(dá)式模式耦合,然后使用該引擎以若干種方式匹配文本:

  • 可以調(diào)用靜態(tài)模式匹配方法,如 xref:System.Text.RegularExpressions.Regex.Match(System.String%2CSystem.String)?displayProperty=nameWithType。 這不需要實(shí)例化正則表達(dá)式對象。

  • 可以實(shí)例化一個(gè) xref:System.Text.RegularExpressions.Regex 對象并調(diào)用已解釋的正則表達(dá)式的實(shí)例模式匹配方法。 這是將正則表達(dá)式引擎綁定到正則表達(dá)式模式的默認(rèn)方法。 如果實(shí)例化 xref:System.Text.RegularExpressions.Regex 對象時(shí)未使用包括 options 標(biāo)記的 xref:System.Text.RegularExpressions.RegexOptions.Compiled 自變量,則會生成此方法。

  • 可以實(shí)例化一個(gè) xref:System.Text.RegularExpressions.Regex 對象并調(diào)用已編譯的正則表達(dá)式的實(shí)例模式匹配方法。 當(dāng)使用包括 xref:System.Text.RegularExpressions.Regex 標(biāo)記的 options 參數(shù)實(shí)例化 xref:System.Text.RegularExpressions.RegexOptions.Compiled 對象時(shí),正則表達(dá)式對象表示已編譯的模式。

  • 可以創(chuàng)建一個(gè)與特定正則表達(dá)式模式緊密耦合的特殊用途的 xref:System.Text.RegularExpressions.Regex 對象,編譯該對象,并將其保存到獨(dú)立程序集中。 為此,可調(diào)用 xref:System.Text.RegularExpressions.Regex.CompileToAssembly*?displayProperty=nameWithType 方法。

這種調(diào)用正則表達(dá)式匹配方法的特殊方式會對應(yīng)用程序產(chǎn)生顯著影響。 以下各節(jié)討論何時(shí)使用靜態(tài)方法調(diào)用、已解釋的正則表達(dá)式和已編譯的正則表達(dá)式,以改進(jìn)應(yīng)用程序的性能。

靜態(tài)正則表達(dá)式

建議將靜態(tài)正則表達(dá)式方法用作使用同一正則表達(dá)式重復(fù)實(shí)例化正則表達(dá)式對象的替代方法。 與正則表達(dá)式對象使用的正則表達(dá)式模式不同,靜態(tài)方法調(diào)用所使用的模式中的操作代碼或已編譯的 Microsoft 中間語言 (MSIL) 由正則表達(dá)式引擎緩存在內(nèi)部。

例如,事件處理程序會頻繁調(diào)用其他方法來驗(yàn)證用戶輸入。 下面的代碼中反映了這一點(diǎn),其中一個(gè) xref:System.Windows.Forms.Button 控件的 xref:System.Windows.Forms.Control.Click 事件用于調(diào)用名為 IsValidCurrency 的方法,該方法檢查用戶是否輸入了后跟至少一個(gè)十進(jìn)制數(shù)的貨幣符號。

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.";
}

下面的示例顯示 IsValidCurrency 方法的一個(gè)非常低效的實(shí)現(xiàn)。 請注意,每個(gè)方法調(diào)用使用相同模式重新實(shí)例化 xref:System.Text.RegularExpressions.Regex 對象。 這反過來意味著,每次調(diào)用該方法時(shí),都必須重新編譯正則表達(dá)式模式。

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);
   }
}

應(yīng)將此低效代碼替換為對靜態(tài) xref:System.Text.RegularExpressions.Regex.IsMatch(System.String%2CSystem.String)?displayProperty=nameWithType 方法的調(diào)用。 這樣便不必在你每次要調(diào)用模式匹配方法時(shí)都實(shí)例化 xref:System.Text.RegularExpressions.Regex 對象,還允許正則表達(dá)式引擎從其緩存中檢索正則表達(dá)式的已編譯版本。

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);
   }
}

默認(rèn)情況下,將緩存最后 15 個(gè)最近使用的靜態(tài)正則表達(dá)式模式。 對于需要大量已緩存的靜態(tài)正則表達(dá)式的應(yīng)用程序,可通過設(shè)置 Regex.CacheSize 屬性來調(diào)整緩存大小。

此示例中使用的正則表達(dá)式 \p{Sc}+\s*\d+ 可驗(yàn)證輸入字符串是否包含一個(gè)貨幣符號和至少一個(gè)十進(jìn)制數(shù)。 模式的定義如下表所示。

模式描述
\p{Sc}+與 Unicode 符號、貨幣類別中的一個(gè)或多個(gè)字符匹配。
\s*匹配零個(gè)或多個(gè)空白字符。
\d+匹配一個(gè)或多個(gè)十進(jìn)制數(shù)字。

已解釋與已編譯的正則表達(dá)式

將解釋未通過 RegexOptions.Compiled 選項(xiàng)的規(guī)范綁定到正則表達(dá)式引擎的正則表達(dá)式模式。 在實(shí)例化正則表達(dá)式對象時(shí),正則表達(dá)式引擎會將正則表達(dá)式轉(zhuǎn)換為一組操作代碼。 調(diào)用實(shí)例方法時(shí),操作代碼會轉(zhuǎn)換為 MSIL 并由 JIT 編譯器執(zhí)行。 同樣,當(dāng)調(diào)用一種靜態(tài)正則表達(dá)式方法并且在緩存中找不到該正則表達(dá)式時(shí),正則表達(dá)式引擎會將該正則表達(dá)式轉(zhuǎn)換為一組操作代碼并將其存儲在緩存中。 然后,它將這些操作代碼轉(zhuǎn)換為 MSIL,以便于 JIT 編譯器執(zhí)行。 已解釋的正則表達(dá)式會減少啟動(dòng)時(shí)間,但會使執(zhí)行速度變慢。 因此,在少數(shù)方法調(diào)用中使用正則表達(dá)式時(shí)或調(diào)用正則表達(dá)式方法的確切數(shù)量未知但預(yù)期很小時(shí),使用已解釋的正則表達(dá)式的效果最佳。 隨著方法調(diào)用數(shù)量的增加,執(zhí)行速度變慢對性能的影響會超過減少啟動(dòng)時(shí)間帶來的性能改進(jìn)。

將編譯通過 RegexOptions.Compiled 選項(xiàng)的規(guī)范綁定到正則表達(dá)式引擎的正則表達(dá)式模式。 這意味著,當(dāng)實(shí)例化正則表達(dá)式對象時(shí)或當(dāng)調(diào)用一種靜態(tài)正則表達(dá)式方法并且在緩存中找不到該正則表達(dá)式時(shí),正則表達(dá)式引擎會將該正則表達(dá)式轉(zhuǎn)換為一組中間操作代碼,這些代碼之后會轉(zhuǎn)換為 MSIL。 調(diào)用方法時(shí),JIT 編譯器將執(zhí)行該 MSIL。 與已解釋的正則表達(dá)式相比,已編譯的正則表達(dá)式增加了啟動(dòng)時(shí)間,但執(zhí)行各種模式匹配方法的速度更快。 因此,相對于調(diào)用的正則表達(dá)式方法的數(shù)量,因編譯正則表達(dá)式而產(chǎn)生的性能產(chǎn)生了改進(jìn)。

簡言之,當(dāng)你使用特定正則表達(dá)式調(diào)用正則表達(dá)式方法相對不頻繁時(shí),建議使用已解釋的正則表達(dá)式。 當(dāng)你使用特定正則表達(dá)式調(diào)用正則表達(dá)式方法相對頻繁時(shí),應(yīng)使用已編譯的正則表達(dá)式。 很難確定已解釋的正則表達(dá)式執(zhí)行速度減慢超出啟動(dòng)時(shí)間減少帶來的性能增益的確切閾值,或已編譯的正則表達(dá)式啟動(dòng)速度減慢超出執(zhí)行速度加快帶來的性能增益的閾值。 這依賴于各種因素,包括正則表達(dá)式的復(fù)雜程度和它處理的特定數(shù)據(jù)。 若要確定已解釋或已編譯的正則表達(dá)式是否可為特定應(yīng)用程序方案提供最佳性能,可以使用 Diagnostics.Stopwatch 類來比較其執(zhí)行時(shí)間。

下面的示例比較了已編譯和已解釋正則表達(dá)式在讀取 Theodore Dreiser 所著《金融家》中前十句文本和所有句文本時(shí)的性能。 如示例輸出所示,當(dāng)只對匹配方法的正則表達(dá)式進(jìn)行十次調(diào)用時(shí),已解釋的正則表達(dá)式與已編譯的正則表達(dá)式相比,可提供更好的性能。 但是,當(dāng)進(jìn)行大量調(diào)用(在此示例中,超過 13,000 次調(diào)用)時(shí),已編譯的正則表達(dá)式可提供更好的性能。

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

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

模式描述
\b在單詞邊界處開始匹配。
\w+匹配一個(gè)或多個(gè)單詞字符。
(\r?\n)|,?\s)匹配零個(gè)或一個(gè)回車符后跟一個(gè)換行符,或零個(gè)或一個(gè)逗號后跟一個(gè)空白字符。
(\w+((\r?\n)|,?\s))*匹配一個(gè)或多個(gè)單詞字符的零個(gè)或多個(gè)事例,后跟零個(gè)或一個(gè)回車符和換行符,或后跟零個(gè)或一個(gè)逗號、一個(gè)空格字符。
\w+匹配一個(gè)或多個(gè)單詞字符。
[.?:;!]匹配句號、問號、冒號、分號或感嘆號。

正則表達(dá)式:編譯為程序集

借助 .NET,還可以創(chuàng)建包含已編譯正則表達(dá)式的程序集。 這樣會將正則表達(dá)式編譯對性能造成的影響從運(yùn)行時(shí)轉(zhuǎn)移到設(shè)計(jì)時(shí)。 但是,這還涉及一些其他工作:必須提前定義正則表達(dá)式并將其編譯為程序集。 然后,編譯器在編譯使用該程序集的正則表達(dá)式的源代碼時(shí),可以引用此程序集。 程序集內(nèi)的每個(gè)已編譯正則表達(dá)式都由從 xref:System.Text.RegularExpressions.Regex 派生的類來表示。

若要將正則表達(dá)式編譯為程序集,可調(diào)用 Regex.CompileToAssembly(RegexCompilationInfo[], AssemblyName) 方法并向其傳遞表示要編譯的正則表達(dá)式的 RegexCompilationInfo 對象數(shù)組和包含有關(guān)要?jiǎng)?chuàng)建的程序集的信息的 AssemblyName 對象。

建議你在以下情況下將正則表達(dá)式編譯為程序集:

  • 如果你是要?jiǎng)?chuàng)建可重用正則表達(dá)式庫的組件開發(fā)人員。

  • 如果你預(yù)期正則表達(dá)式的模式匹配方法要被調(diào)用的次數(shù)無法確定 -- 從任意位置,次數(shù)可能為一次兩次到上千上萬次。 與已編譯或已解釋的正則表達(dá)式不同,編譯為單獨(dú)程序集的正則表達(dá)式可提供與方法調(diào)用數(shù)量無關(guān)的一致性能。

如果使用已編譯的正則表達(dá)式來優(yōu)化性能,則不應(yīng)使用反射來創(chuàng)建程序集,加載正則表達(dá)式引擎并執(zhí)行其模式匹配方法。 這要求你避免動(dòng)態(tài)生成正則表達(dá)式模式,并且要在創(chuàng)建程序集時(shí)指定模式匹配選項(xiàng)(如不區(qū)分大小寫的模式匹配)。 它還要求將創(chuàng)建程序集的代碼與使用正則表達(dá)式的代碼分離。

下面的示例演示如何創(chuàng)建包含已編譯的正則表達(dá)式的程序集。 它創(chuàng)建包含一個(gè)正則表達(dá)式類 SentencePattern 的程序集 RegexLib.dll,其中包含已解釋與已編譯的正則表達(dá)式部分中使用的句子匹配的正則表達(dá)式模式。

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);
   }
}

在將示例編譯為可執(zhí)行文件并運(yùn)行時(shí),它會創(chuàng)建一個(gè)名為 RegexLib.dll 的程序集。 正則表達(dá)式用名為 Utilities.RegularExpressions.SentencePattern 并由 RegularExpressions.Regex 派生的類來表示。 然后,下面的示例使用已編譯正則表達(dá)式,從 Theodore Dreiser 所著《金融家》文本中提取句子。

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.

控制回溯

通常,正則表達(dá)式引擎使用線性進(jìn)度在輸入字符串中移動(dòng)并將其編譯為正則表達(dá)式模式。 但是,當(dāng)在正則表達(dá)式模式中使用不確定限定符(如 *、+ 和 ?)時(shí),正則表達(dá)式引擎可能會放棄一部分成功的分部匹配,并返回以前保存的狀態(tài),以便為整個(gè)模式搜索成功匹配。 此過程稱為回溯。

支持回溯可為正則表達(dá)式提供強(qiáng)大的功能和靈活性。 還可將控制正則表達(dá)式引擎操作的職責(zé)交給正則表達(dá)式開發(fā)人員來處理。 由于開發(fā)人員通常不了解此職責(zé),因此其誤用回溯或依賴過多回溯通常會顯著降低正則表達(dá)式的性能。 在最糟糕的情況下,輸入字符串中每增加一個(gè)字符,執(zhí)行時(shí)間會加倍。 實(shí)際上,如果過多使用回溯,則在輸入與正則表達(dá)式模式近似匹配時(shí)很容易創(chuàng)建無限循環(huán)的編程等效形式;正則表達(dá)式引擎可能需要幾小時(shí)甚至幾天來處理相對短的輸入字符串。

通常,盡管回溯不是匹配所必需的,但應(yīng)用程序會因使用回溯而對性能產(chǎn)生負(fù)面影響。 例如,正則表達(dá)式 \b\p{Lu}\w*\b 將匹配以大寫字符開頭的所有單詞,如下表所示。

模式描述
\b在單詞邊界處開始匹配。
\p{Lu}匹配大寫字符。
\w*匹配零個(gè)或多個(gè)單詞字符。
\b在單詞邊界處結(jié)束匹配。

由于單詞邊界與單詞字符不同也不是其子集,因此正則表達(dá)式引擎在匹配單詞字符時(shí)無法跨越單詞邊界。 這意味著,對于此正則表達(dá)式而言,回溯對任何匹配的總體成功不會有任何貢獻(xiàn) -- 由于正則表達(dá)式引擎被強(qiáng)制為單詞字符的每個(gè)成功的初步匹配保存其狀態(tài),因此它只會降低性能。

如果確定不需要回溯,可使用 (?>subexpression) 語言元素(被稱為原子組)來禁用它。 下面的示例通過使用兩個(gè)正則表達(dá)式來分析輸入字符串。 第一個(gè)正則表達(dá)式 \b\p{Lu}\w*\b 依賴于回溯。 第二個(gè)正則表達(dá)式 \b\p{Lu}(?>\w*)\b 禁用回溯。 如示例輸出所示,這兩個(gè)正則表達(dá)式產(chǎn)生的結(jié)果相同。

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

在許多情況下,在將正則表達(dá)式模式與輸入文本匹配時(shí),回溯很重要。 但是,過度回溯會嚴(yán)重降低性能,并且會產(chǎn)生應(yīng)用程序已停止響應(yīng)的感覺。 特別需要指出的是,當(dāng)嵌套限定符并且與外部子表達(dá)式匹配的文本為與內(nèi)部子表達(dá)式匹配的文本的子集時(shí),尤其會出現(xiàn)這種情況。

例如,正則表達(dá)式模式 ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ 用于匹配至少包括一個(gè)字母數(shù)字字符的部件號。 任何附加字符可以包含字母數(shù)字字符、連字符、下劃線或句號,但最后一個(gè)字符必須為字母數(shù)字。 美元符號用于終止部件號。 在某些情況下,由于限定符嵌套并且子表達(dá)式 [0-9A-Z] 是子表達(dá)式 [-.\w]* 的子集,因此此正則表達(dá)式模式會表現(xiàn)出極差的性能。

在這些情況下,可通過移除嵌套限定符并將外部子表達(dá)式替換為零寬度預(yù)測先行和回顧斷言來優(yōu)化正則表達(dá)式性能。 預(yù)測先行和回顧斷言是定位點(diǎn);它們不在輸入字符串中移動(dòng)指針,而是通過預(yù)測先行或回顧來檢查是否滿足指定條件。 例如,可將部件號正則表達(dá)式重寫為 ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$。 此正則表達(dá)式模式的定義如下表所示。

模式描述
^從輸入字符串的開頭部分開始匹配。
[0-9A-Z]匹配字母數(shù)字字符。 部件號至少要包含此字符。
[-.\w]*匹配零個(gè)或多個(gè)任意單詞字符、連字符或句號。
\$匹配美元符號。
(?<=[0-9A-Z])查看作為結(jié)束的美元符號,以確保前一個(gè)字符是字母數(shù)字。
$在輸入字符串末尾結(jié)束匹配。

下面的示例演示了如何使用此正則表達(dá)式來匹配包含可能部件號的數(shù)組。

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.

.NET 中的正則表達(dá)式語言包括以下可用于消除嵌套限定符的語言元素。 有關(guān)詳細(xì)信息,請參閱分組構(gòu)造

語言元素描述
(?= subexpression )零寬度正預(yù)測先行。 預(yù)測先行當(dāng)前位置,以確定 subexpression 是否與輸入字符串匹配。
(?! subexpression )零寬度負(fù)預(yù)測先行。 預(yù)測先行當(dāng)前位置,以確定 subexpression 是否不與輸入字符串匹配。
(?<= subexpression )零寬度正回顧。 回顧后發(fā)當(dāng)前位置,以確定 subexpression 是否與輸入字符串匹配。
(?<! subexpression )零寬度負(fù)回顧。 回顧后發(fā)當(dāng)前位置,以確定 subexpression 是否不與輸入字符串匹配。

使用超時(shí)值

如果正則表達(dá)式處理與正則表達(dá)式模式大致匹配的輸入,則通常依賴于會嚴(yán)重影響其性能的過度回溯。 除認(rèn)真考慮對回溯的使用以及針對大致匹配輸入對正則表達(dá)式進(jìn)行測試之外,還應(yīng)始終設(shè)置一個(gè)超時(shí)值以確保最大程度地降低過度回溯的影響(如果有)。

正則表達(dá)式超時(shí)間隔定義了在超時(shí)前正則表達(dá)式引擎用于查找單個(gè)匹配項(xiàng)的時(shí)間長度。默認(rèn)超時(shí)間隔為 Regex.InfiniteMatchTimeout,這意味著正則表達(dá)式不會超時(shí)。可以按如下所示重寫此值并定義超時(shí)間隔:

  • 在實(shí)例化一個(gè) Regex 對象(通過調(diào)用 Regex(String, RegexOptions, TimeSpan) 構(gòu)造函數(shù))時(shí),提供一個(gè)超時(shí)值。
  • 調(diào)用靜態(tài)模式匹配方法,如 Regex.Match(String, String, RegexOptions, TimeSpan) 或 Regex.Replace(String, String, String, RegexOptions, TimeSpan),其中包含 matchTimeout 參數(shù)。
  • 對于通過調(diào)用 Regex.CompileToAssembly 方法創(chuàng)建的已編譯的正則表達(dá)式,可調(diào)用帶有 TimeSpan 類型的參數(shù)的構(gòu)造函數(shù)。

如果定義了超時(shí)間隔并且在此間隔結(jié)束時(shí)未找到匹配項(xiàng),則正則表達(dá)式方法將引發(fā) RegexMatchTimeoutException 異常。 在異常處理程序中,可以選擇使用一個(gè)更長的超時(shí)間隔來重試匹配、放棄匹配嘗試并假定沒有匹配項(xiàng),或者放棄匹配嘗試并記錄異常信息以供未來分析。

下面的示例定義了一種 GetWordData 方法,此方法實(shí)例化了一個(gè)正則表達(dá)式,使其具有 350 毫秒的超時(shí)間隔,用于計(jì)算文本文件中的詞語數(shù)和一個(gè)詞語中的平均字符數(shù)。 如果匹配操作超時(shí),則超時(shí)間隔將延長 350 毫秒并重新實(shí)例化 Regex 對象。 如果新的超時(shí)間隔超過 1 秒,則此方法將再次向調(diào)用方引發(fā)異常。

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);
   }
}

只在必要時(shí)捕獲

.NET 中的正則表達(dá)式支持許多分組構(gòu)造,這樣,便可以將正則表達(dá)式模式分組為一個(gè)或多個(gè)子表達(dá)式。 .NET 正則表達(dá)式語言中最常用的分組構(gòu)造為 (subexpression)(用于定義編號捕獲組)和 (?subexpression)(用于定義命名捕獲組)。 分組構(gòu)造是創(chuàng)建反向引用和定義要應(yīng)用限定符的子表達(dá)式時(shí)所必需的。

但是,使用這些語言元素會產(chǎn)生一定的開銷。 它們會導(dǎo)致用最近的未命名或已命名捕獲來填充 GroupCollection 屬性返回的 Match.Groups 對象,如果單個(gè)分組構(gòu)造已捕獲輸入字符串中的多個(gè)子字符串,則還會填充包含多個(gè) CaptureCollection 對象的特定捕獲組的 Group.Captures 屬性返回的 Capture 對象。

通常,只在正則表達(dá)式中使用分組構(gòu)造,這樣可對其應(yīng)用限定符,而且以后不會使用這些子表達(dá)式捕獲的組。 例如,正則表達(dá)式 \b(\w+[;,]?\s?)+[.?!] 用于捕獲整個(gè)句子。 下表描述了此正則表達(dá)式模式中的語言元素及其對 Match 對象的 Match.Groups 和 Group.Captures 集合的影響。

模式描述
\b在單詞邊界處開始匹配。
\w+匹配一個(gè)或多個(gè)單詞字符。
[;,]?匹配零個(gè)或一個(gè)逗號或分號。
\s?匹配零個(gè)或一個(gè)空白字符。
(\w+[;,]?\s?)+匹配以下一個(gè)或多個(gè)事例:一個(gè)或多個(gè)單詞字符,后跟一個(gè)可選逗號或分號,一個(gè)可選的空白字符。 用于定義第一個(gè)捕獲組,它是必需的,以便將重復(fù)多個(gè)單詞字符的組合(即單詞)后跟可選標(biāo)點(diǎn)符號,直至正則表達(dá)式引擎到達(dá)句子末尾。
[.?!]匹配句號、問號或感嘆號。

如下面的示例所示,當(dāng)找到匹配時(shí),GroupCollection 和 CaptureCollection 對象都將用匹配中的捕獲內(nèi)容來填充。 在此情況下,存在捕獲組 (\w+[;,]?\s?),因此可對其應(yīng)用 + 限定符,從而使得正則表達(dá)式模式可與句子中的每個(gè)單詞匹配。 否則,它將匹配句子中的最后一個(gè)單詞。

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.

當(dāng)你只使用子表達(dá)式來對其應(yīng)用限定符并且你對捕獲的文本不感興趣時(shí),應(yīng)禁用組捕獲。 例如,(?:subexpression) 語言元素可防止應(yīng)用此元素的組捕獲匹配的子字符串。 在下面的示例中,上一示例中的正則表達(dá)式模式更改為 \b(?:\w+[;,]?\s?)+[.?!]。 正如輸出所示,它禁止正則表達(dá)式引擎填充 GroupCollection 和 CaptureCollection 集合。

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.

可以通過以下方式之一來禁用捕獲:

  • 使用 (?:subexpression) 語言元素。 此元素可防止在它應(yīng)用的組中捕獲匹配的子字符串。 它不在任何嵌套的組中禁用子字符串捕獲。
  • 使用 ExplicitCapture 選項(xiàng)。 在正則表達(dá)式模式中禁用所有未命名或隱式捕獲。 使用此選項(xiàng)時(shí),只能捕獲與使用 (?subexpression) 語言元素定義的命名組匹配的子字符串。 可將 ExplicitCapture 標(biāo)記傳遞給 options 類構(gòu)造函數(shù)的 Regex 參數(shù)或 options 靜態(tài)匹配方法的 Regex 參數(shù)。
  • 在 n 語言元素中使用 (?imnsx) 選項(xiàng)。 此選項(xiàng)將在元素出現(xiàn)的正則表達(dá)式模式中的點(diǎn)處禁用所有未命名或隱式捕獲。 捕獲將一直禁用到模式結(jié)束或 (-n) 選項(xiàng)啟用未命名或隱式捕獲。 有關(guān)詳細(xì)信息,請參閱 其他構(gòu)造。
  • 在 n 語言元素中使用 (?imnsx:subexpression) 選項(xiàng)。 此選項(xiàng)可在 subexpression 中禁用所有未命名或隱式捕獲。 同時(shí)禁用任何未命名或隱式的嵌套捕獲組進(jìn)行的任何捕獲。

原文鏈接

到此這篇關(guān)于.NET正則表達(dá)式最佳用法的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Linux Ubuntu系統(tǒng)上手動(dòng)安裝.NET Core SDK的方法

    Linux Ubuntu系統(tǒng)上手動(dòng)安裝.NET Core SDK的方法

    .NET Core是一個(gè)開源通用的開發(fā)框架,支持跨平臺,即支持在Window,macOS,Linux等系統(tǒng)上的開發(fā)和部署,并且可以在硬件設(shè)備,云服務(wù),和嵌入式/物聯(lián)網(wǎng)方案中進(jìn)行使用。下面這篇文章將給大家詳細(xì)介紹關(guān)于在Linux Ubuntu系統(tǒng)上手動(dòng)安裝.NET Core SDK的方法。
    2016-12-12
  • WPF圖表LiveChart使用詳解

    WPF圖表LiveChart使用詳解

    本文詳細(xì)講解了WPF圖表LiveChart的用法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-01-01
  • asp.net Webconfig中的一些配置

    asp.net Webconfig中的一些配置

    除了手動(dòng)編輯此文件以外,您還可以使用Web 管理工具來配置應(yīng)用程序的設(shè)置??梢允褂?Visual Studio 中的“網(wǎng)站”->“Asp.Net 配置”選項(xiàng)。
    2010-07-07
  • asp.net實(shí)例代碼protected override void Render(HtmlTextWriter writer)

    asp.net實(shí)例代碼protected override void Render(HtmlTextWriter wri

    把最終要輸出的html壓縮后再輸出和最終輸出前先存為html文件,如果發(fā)布最新信息了,可以打開一次default.aspx,然后他又會生成一次html
    2008-08-08
  • 部署ASP.NET?Core程序到Windows系統(tǒng)

    部署ASP.NET?Core程序到Windows系統(tǒng)

    這篇文章介紹了部署ASP.NET?Core程序到Windows系統(tǒng)的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-03-03
  • ASP.NET 中ImageMap控件的用法

    ASP.NET 中ImageMap控件的用法

    本文主要介紹ImageMap控件的使用方法,并做了簡單的代碼演示,希望能幫到大家。
    2016-04-04
  • SqlConnection.ConnectionString相關(guān)關(guān)鍵字

    SqlConnection.ConnectionString相關(guān)關(guān)鍵字

    SqlConnection.ConnectionString相關(guān)關(guān)鍵字...
    2007-01-01
  • Blazor組件事件處理功能

    Blazor組件事件處理功能

    這篇文章介紹了Blazor組件的事件處理功能,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-01-01
  • ASP.NET中MultiView和View選項(xiàng)卡控件的使用方法

    ASP.NET中MultiView和View選項(xiàng)卡控件的使用方法

    ASP.NET中的MultiView和View控件可以作為承載其他控件的容器,一般我們都很少使用,本文主要介紹使用MultiView和View實(shí)現(xiàn)選項(xiàng)卡效果。
    2016-04-04
  • ASP.NET?CORE基礎(chǔ)教程

    ASP.NET?CORE基礎(chǔ)教程

    本文詳細(xì)講解了ASP.NET?CORE的基礎(chǔ)教程,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-01-01

最新評論