【问题标题】:How to split csv whose columns may contain ,如何拆分其列可能包含的csv,
【发布时间】:2023-03-11 07:07:01
【问题描述】:

给定

2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,"Corvallis, OR",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34

如何使用C#将以上信息拆分成字符串如下:

2
1016
7/31/2008 14:22
Geoff Dalgas
6/5/2011 22:21
http://stackoverflow.com
Corvallis, OR
7679
351
81
b437f461b3fd27387c5d8ab47a293d35
34

如您所见,其中一列包含 ,

更新

基于 C# Regex Split - commas outside quotes

string[] result = Regex.Split(samplestring, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

【问题讨论】:

  • 虽然在 Java 中,类似的问题:stackoverflow.com/questions/1757065/…
  • 使用正则表达式来做到这一点是个坏建议。 .NET Framework 已经内置支持解析 CSV。请参阅此答案,这是您应该接受的答案。否则我会把它当作stackoverflow.com/questions/3147836/… 的骗子来关闭,这同样是错误的。
  • 您能否详细说明 .NET 对解析带有嵌入式逗号的 CSV 文件的内置支持是什么?您指的是 Microsoft.VisualBasic.FileIO.TextFieldParser 类吗?
  • 这能回答你的问题吗? Reading CSV files using C#
  • 另一个基于Regex 的解决方案here 有一些很好的例子。

标签: c# .net csv


【解决方案1】:

使用Microsoft.VisualBasic.FileIO.TextFieldParser 类。这将处理解析分隔文件TextReaderStream,其中一些字段用引号引起来,而另一些则没有。

例如:

using Microsoft.VisualBasic.FileIO;

string csv = "2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,\"Corvallis, OR\",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34";

TextFieldParser parser = new TextFieldParser(new StringReader(csv));

// You can also read from a file
// TextFieldParser parser = new TextFieldParser("mycsvfile.csv");

parser.HasFieldsEnclosedInQuotes = true;
parser.SetDelimiters(",");

string[] fields;

while (!parser.EndOfData)
{
    fields = parser.ReadFields();
    foreach (string field in fields)
    {
        Console.WriteLine(field);
    }
} 

parser.Close();

这应该会产生以下输出:

2 1016 2008 年 7 月 31 日 14:22 杰夫·达尔加斯 2011 年 6 月 5 日 22:21 http://stackoverflow.com 科瓦利斯,或 7679 351 81 b437f461b3fd27387c5d8ab47a293d35 34

更多信息请参见Microsoft.VisualBasic.FileIO.TextFieldParser

您需要在 Add References .NET 选项卡中添加对 Microsoft.VisualBasic 的引用。

【讨论】:

  • 老兄,非常感谢您的解决方案,我有大约 500K+ 行 CSV 数据需要加载到表中,并且它加载了引号内包含的逗号。如果我们的道路交叉,我欠你一杯成人饮料。
  • @tim 我使用了这个,并注意到它跳过了所有偶数行号,只处理具有 1050 行的文件中的奇数行号。有什么想法吗?
  • @Smith - 没有看到您的代码或示例输入,我不知道。我建议发布一个新问题。也许文件在偶数行上缺少回车符或其他行尾标记?
  • 直到我看到这个我才知道这个库 - 谢谢!如果其他人想要一个解析整个 CSV 文件的示例,请参阅这个 SO 答案:stackoverflow.com/a/3508572/3105807
  • 我们能不能因为微软没有提供一个接受字符串的构造函数,所以我们必须先跳过将它转换为流的障碍?否则,很好的答案。
【解决方案2】:

已经很晚了,但这可能对某人有所帮助。我们可以像下面这样使用 RegEx。

Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
String[] Fields = CSVParser.Split(Test);

【讨论】:

  • 这是完美的。宁愿使用它也不愿导入整个其他库。太棒了。
  • 匹配 asdf, "", "as,\"df",
  • 这个解决方案不能正常工作 - 它没有考虑语音标记,这意味着在阅读过程中会在不正确的位置有很多语音标记。
  • 如果某行中缺少结尾引号怎么办:asd,"","as,\"df","asd asd","as
  • 这对我有用,并考虑了引用的语音标记。 3000 万行。非常好,代码量最少。
【解决方案3】:

我看到如果您在 Excel 中粘贴 csv 分隔文本并执行“文本到列”,它会要求您提供“文本限定符”。它默认为双引号,因此它将双引号内的文本视为文字。我想Excel通过一次一个字符来实现这一点,如果遇到“文本限定符”,它会继续进入下一个“限定符”。您可以自己使用 for 循环和布尔值来表示您是否在文字文本中。

public string[] CsvParser(string csvText)
{
    List<string> tokens = new List<string>();

    int last = -1;
    int current = 0;
    bool inText = false;

    while(current < csvText.Length)
    {
        switch(csvText[current])
        {
            case '"':
                inText = !inText; break;
            case ',':
                if (!inText) 
                {
                    tokens.Add(csvText.Substring(last + 1, (current - last)).Trim(' ', ',')); 
                    last = current;
                }
                break;
            default:
                break;
        }
        current++;
    }

    if (last != csvText.Length - 1) 
    {
        tokens.Add(csvText.Substring(last+1).Trim());
    }

    return tokens.ToArray();
}

【讨论】:

  • 单个 ,.逗号应该产生 2 个空字段,但它不会
【解决方案4】:

您可以拆分所有后面有偶数个引号的逗号。

您还想在specf 上查看有关处理逗号的 CSV 格式。

有用的链接:C# Regex Split - commas outside quotes

【讨论】:

  • @q0987 - 这不是正确的答案。框架中内置了对此的支持:stackoverflow.com/questions/6542996/…
  • @Kev - 这也不正确。您链接到的答案是针对 Microsoft.VisualBasic.FileIO.TextFieldParser 类,该类未内置在 .NET Framework 中。这就是您必须引用 Microsoft.VisualBasic 程序集才能在您的项目中使用它的原因。
  • @BlairAllen 仅仅因为它位于不同的命名空间和程序集中并不意味着它不是 Microsoft .NET Framework 提供的功能。现在,如果我说它是基类库的一部分,那么您可能说得有道理。
【解决方案5】:

使用像 LumenWorks 这样的库来阅读 CSV。它将处理带有引号的字段,并且由于已经存在很长时间,因此总体上可能比您的自定义解决方案更强大。

【讨论】:

    【解决方案6】:

    当 .csv 文件可能是逗号分隔的字符串、逗号分隔的引号字符串或两者的混乱组合时,解析 .csv 文件是一件棘手的事情。我想出的解决方案允许三种可能性中的任何一种。

    我创建了一个方法 ParseCsvRow(),它从 csv 字符串返回一个数组。我首先通过将双引号上的字符串拆分为一个名为quotesArray 的数组来处理字符串中的双引号。带引号的字符串 .csv 文件仅在双引号数为偶数时才有效。列值中的双引号应替换为一对双引号(这是 Excel 的方法)。只要 .csv 文件满足这些要求,您就可以预期分隔符逗号仅出现在双引号对之外。双引号对内的逗号是列值的一部分,在将 .csv 拆分为数组时应忽略。

    我的方法将通过仅查看引号数组的偶数索引来测试双引号对之外的逗号。它还会从列值的开头和结尾删除双引号。

        public static string[] ParseCsvRow(string csvrow)
        {
            const string obscureCharacter = "ᖳ";
            if (csvrow.Contains(obscureCharacter)) throw new Exception("Error: csv row may not contain the " + obscureCharacter + " character");
    
            var unicodeSeparatedString = "";
    
            var quotesArray = csvrow.Split('"');  // Split string on double quote character
            if (quotesArray.Length > 1)
            {
                for (var i = 0; i < quotesArray.Length; i++)
                {
                    // CSV must use double quotes to represent a quote inside a quoted cell
                    // Quotes must be paired up
                    // Test if a comma lays outside a pair of quotes.  If so, replace the comma with an obscure unicode character
                    if (Math.Round(Math.Round((decimal) i/2)*2) == i)
                    {
                        var s = quotesArray[i].Trim();
                        switch (s)
                        {
                            case ",":
                                quotesArray[i] = obscureCharacter;  // Change quoted comma seperated string to quoted "obscure character" seperated string
                                break;
                        }
                    }
                    // Build string and Replace quotes where quotes were expected.
                    unicodeSeparatedString += (i > 0 ? "\"" : "") + quotesArray[i].Trim();
                }
            }
            else
            {
                // String does not have any pairs of double quotes.  It should be safe to just replace the commas with the obscure character
                unicodeSeparatedString = csvrow.Replace(",", obscureCharacter);
            }
    
            var csvRowArray = unicodeSeparatedString.Split(obscureCharacter[0]); 
    
            for (var i = 0; i < csvRowArray.Length; i++)
            {
                var s = csvRowArray[i].Trim();
                if (s.StartsWith("\"") && s.EndsWith("\""))
                {
                    csvRowArray[i] = s.Length > 2 ? s.Substring(1, s.Length - 2) : "";  // Remove start and end quotes.
                }
            }
    
            return csvRowArray;
        }
    

    我的方法的一个缺点是我暂时将分隔符逗号替换为晦涩的 Unicode 字符。这个字符需要非常模糊,它永远不会出现在您的 .csv 文件中。您可能需要对此进行更多处理。

    【讨论】:

      【解决方案7】:

      我在 CSV 中遇到了一个问题,其中包含带有引号字符的字段,因此使用 TextFieldParser,我想出了以下内容:

      private static string[] parseCSVLine(string csvLine)
      {
        using (TextFieldParser TFP = new TextFieldParser(new MemoryStream(Encoding.UTF8.GetBytes(csvLine))))
        {
          TFP.HasFieldsEnclosedInQuotes = true;
          TFP.SetDelimiters(",");
      
          try 
          {           
            return TFP.ReadFields();
          }
          catch (MalformedLineException)
          {
            StringBuilder m_sbLine = new StringBuilder();
      
            for (int i = 0; i < TFP.ErrorLine.Length; i++)
            {
              if (i > 0 && TFP.ErrorLine[i]== '"' &&(TFP.ErrorLine[i + 1] != ',' && TFP.ErrorLine[i - 1] != ','))
                m_sbLine.Append("\"\"");
              else
                m_sbLine.Append(TFP.ErrorLine[i]);
            }
      
            return parseCSVLine(m_sbLine.ToString());
          }
        }
      }
      

      仍然使用StreamReader逐行读取CSV,如下:

      using(StreamReader SR = new StreamReader(FileName))
      {
        while (SR.Peek() >-1)
          myStringArray = parseCSVLine(SR.ReadLine());
      }
      

      【讨论】:

        【解决方案8】:

        使用Cinchoo ETL - 一个开源库,它可以自动处理包含分隔符的列值。

        string csv = @"2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,""Corvallis, OR"",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34";
        
        using (var p = ChoCSVReader.LoadText(csv)
            )
        {
            Console.WriteLine(p.Dump());
        }
        

        输出:

        Key: Column1 [Type: String]
        Value: 2
        Key: Column2 [Type: String]
        Value: 1016
        Key: Column3 [Type: String]
        Value: 7/31/2008 14:22
        Key: Column4 [Type: String]
        Value: Geoff Dalgas
        Key: Column5 [Type: String]
        Value: 6/5/2011 22:21
        Key: Column6 [Type: String]
        Value: http://stackoverflow.com
        Key: Column7 [Type: String]
        Value: Corvallis, OR
        Key: Column8 [Type: String]
        Value: 7679
        Key: Column9 [Type: String]
        Value: 351
        Key: Column10 [Type: String]
        Value: 81
        Key: Column11 [Type: String]
        Value: b437f461b3fd27387c5d8ab47a293d35
        Key: Column12 [Type: String]
        Value: 34
        

        更多信息,请访问 codeproject 文章。

        希望对你有帮助。

        【讨论】:

          【解决方案9】:

          这个问题及其重复问题有很多答案。我尝试了this one that looked promising,但在其中发现了一些错误。我对它进行了大量修改,使其能够通过我的所有测试。

              /// <summary>
              /// Returns a collection of strings that are derived by splitting the given source string at
              /// characters given by the 'delimiter' parameter.  However, a substring may be enclosed between
              /// pairs of the 'qualifier' character so that instances of the delimiter can be taken as literal
              /// parts of the substring.  The method was originally developed to split comma-separated text
              /// where quotes could be used to qualify text that contains commas that are to be taken as literal
              /// parts of the substring.  For example, the following source:
              ///     A, B, "C, D", E, "F, G"
              /// would be split into 5 substrings:
              ///     A
              ///     B
              ///     C, D
              ///     E
              ///     F, G
              /// When enclosed inside of qualifiers, the literal for the qualifier character may be represented
              /// by two consecutive qualifiers.  The two consecutive qualifiers are distinguished from a closing
              /// qualifier character.  For example, the following source:
              ///     A, "B, ""C"""
              /// would be split into 2 substrings:
              ///     A
              ///     B, "C"
              /// </summary>
              /// <remarks>Originally based on: https://stackoverflow.com/a/43284485/2998072</remarks>
              /// <param name="source">The string that is to be split</param>
              /// <param name="delimiter">The character that separates the substrings</param>
              /// <param name="qualifier">The character that is used (in pairs) to enclose a substring</param>
              /// <param name="toTrim">If true, then whitespace is removed from the beginning and end of each
              /// substring.  If false, then whitespace is preserved at the beginning and end of each substring.
              /// </param>
              public static List<String> SplitQualified(this String source, Char delimiter, Char qualifier,
                                          Boolean toTrim)
              {
                  // Avoid throwing exception if the source is null
                  if (String.IsNullOrEmpty(source))
                      return new List<String> { "" };
          
                  var results = new List<String>();
                  var result = new StringBuilder();
                  Boolean inQualifier = false;
          
                  // The algorithm is designed to expect a delimiter at the end of each substring, but the
                  // expectation of the caller is that the final substring is not terminated by delimiter.
                  // Therefore, we add an artificial delimiter at the end before looping through the source string.
                  String sourceX = source + delimiter;
          
                  // Loop through each character of the source
                  for (var idx = 0; idx < sourceX.Length; idx++)
                  {
                      // If current character is a delimiter
                      // (except if we're inside of qualifiers, we ignore the delimiter)
                      if (sourceX[idx] == delimiter && inQualifier == false)
                      {
                          // Terminate the current substring by adding it to the collection
                          // (trim if specified by the method parameter)
                          results.Add(toTrim ? result.ToString().Trim() : result.ToString());
                          result.Clear();
                      }
                      // If current character is a qualifier
                      else if (sourceX[idx] == qualifier)
                      {
                          // ...and we're already inside of qualifier
                          if (inQualifier)
                          {
                              // check for double-qualifiers, which is escape code for a single
                              // literal qualifier character.
                              if (idx + 1 < sourceX.Length && sourceX[idx + 1] == qualifier)
                              {
                                  idx++;
                                  result.Append(sourceX[idx]);
                                  continue;
                              }
                              // Since we found only a single qualifier, that means that we've
                              // found the end of the enclosing qualifiers.
                              inQualifier = false;
                              continue;
                          }
                          else
                              // ...we found an opening qualifier
                              inQualifier = true;
                      }
                      // If current character is neither qualifier nor delimiter
                      else
                          result.Append(sourceX[idx]);
                  }
          
                  return results;
              }
          

          以下是证明它有效的测试方法:

              [TestMethod()]
              public void SplitQualified_00()
              {
                  // Example with no substrings
                  String s = "";
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "" }, substrings);
              }
              [TestMethod()]
              public void SplitQualified_00A()
              {
                  // just a single delimiter
                  String s = ",";
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "", "" }, substrings);
              }
              [TestMethod()]
              public void SplitQualified_01()
              {
                  // Example with no whitespace or qualifiers
                  String s = "1,2,3,1,2,3";
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2", "3", "1", "2", "3" }, substrings);
              }
              [TestMethod()]
              public void SplitQualified_02()
              {
                  // Example with whitespace and no qualifiers
                  String s = " 1, 2 ,3,  1  ,2\t,   3   ";
                  // whitespace should be removed
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2", "3", "1", "2", "3" }, substrings);
              }
              [TestMethod()]
              public void SplitQualified_03()
              {
                  // Example with whitespace and no qualifiers
                  String s = " 1, 2 ,3,  1  ,2\t,   3   ";
                  // whitespace should be preserved
                  var substrings = s.SplitQualified(',', '"', false);
                  CollectionAssert.AreEquivalent(
                      new List<String> { " 1", " 2 ", "3", "  1  ", "2\t", "   3   " },
                      substrings);
              }
              [TestMethod()]
              public void SplitQualified_04()
              {
                  // Example with no whitespace and trivial qualifiers.
                  String s = "1,\"2\",3,1,2,\"3\"";
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2", "3", "1", "2", "3" }, substrings);
          
                  s = "\"1\",\"2\",3,1,\"2\",3";
                  substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2", "3", "1", "2", "3" }, substrings);
              }
              [TestMethod()]
              public void SplitQualified_05()
              {
                  // Example with no whitespace and qualifiers that enclose delimiters
                  String s = "1,\"2,2a\",3,1,2,\"3,3a\"";
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2,2a", "3", "1", "2", "3,3a" },
                                          substrings);
          
                  s = "\"1,1a\",\"2,2b\",3,1,\"2,2c\",3";
                  substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1,1a", "2,2b", "3", "1", "2,2c", "3" },
                                          substrings);
              }
              [TestMethod()]
              public void SplitQualified_06()
              {
                  // Example with qualifiers enclosing whitespace but no delimiter
                  String s = "\" 1 \",\"2 \",3,1,2,\"\t3\t\"";
                  // whitespace should be removed
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2", "3", "1", "2", "3" },
                                          substrings);
              }
              [TestMethod()]
              public void SplitQualified_07()
              {
                  // Example with qualifiers enclosing whitespace but no delimiter
                  String s = "\" 1 \",\"2 \",3,1,2,\"\t3\t\"";
                  // whitespace should be preserved
                  var substrings = s.SplitQualified(',', '"', false);
                  CollectionAssert.AreEquivalent(new List<String> { " 1 ", "2 ", "3", "1", "2", "\t3\t" },
                                          substrings);
              }
              [TestMethod()]
              public void SplitQualified_08()
              {
                  // Example with qualifiers enclosing whitespace but no delimiter; also whitespace btwn delimiters
                  String s = "\" 1 \", \"2 \"  ,  3,1, 2 ,\"  3  \"";
                  // whitespace should be removed
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2", "3", "1", "2", "3" },
                                          substrings);
              }
              [TestMethod()]
              public void SplitQualified_09()
              {
                  // Example with qualifiers enclosing whitespace but no delimiter; also whitespace btwn delimiters
                  String s = "\" 1 \", \"2 \"  ,  3,1, 2 ,\"  3  \"";
                  // whitespace should be preserved
                  var substrings = s.SplitQualified(',', '"', false);
                  CollectionAssert.AreEquivalent(new List<String> { " 1 ", " 2   ", "  3", "1", " 2 ", "  3  " },
                                          substrings);
              }
              [TestMethod()]
              public void SplitQualified_10()
              {
                  // Example with qualifiers enclosing whitespace and delimiter
                  String s = "\" 1 \",\"2 , 2b \",3,1,2,\"  3,3c  \"";
                  // whitespace should be removed
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2 , 2b", "3", "1", "2", "3,3c" },
                                          substrings);
              }
              [TestMethod()]
              public void SplitQualified_11()
              {
                  // Example with qualifiers enclosing whitespace and delimiter; also whitespace btwn delimiters
                  String s = "\" 1 \", \"2 , 2b \"  ,  3,1, 2 ,\"  3,3c  \"";
                  // whitespace should be preserved
                  var substrings = s.SplitQualified(',', '"', false);
                  CollectionAssert.AreEquivalent(new List<String> { " 1 ", " 2 , 2b   ", "  3", "1", " 2 ", "  3,3c  " },
                                          substrings);
              }
              [TestMethod()]
              public void SplitQualified_12()
              {
                  // Example with tab characters between delimiters
                  String s = "\t1,\t2\t,3,1,\t2\t,\t3\t";
                  // whitespace should be removed
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2", "3", "1", "2", "3" }, substrings);
              }
              [TestMethod()]
              public void SplitQualified_13()
              {
                  // Example with newline characters between delimiters
                  String s = "\n1,\n2\n,3,1,\n2\n,\n3\n";
                  // whitespace should be removed
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2", "3", "1", "2", "3" }, substrings);
              }
              [TestMethod()]
              public void SplitQualified_14()
              {
                  // Example with qualifiers enclosing whitespace and delimiter, plus escaped qualifier
                  String s = "\" 1 \",\"\"\"2 , 2b \"\"\",3,1,2,\"  \"\"3,3c  \"";
                  // whitespace should be removed
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "\"2 , 2b \"", "3", "1", "2", "\"3,3c" },
                                          substrings);
              }
              [TestMethod()]
              public void SplitQualified_14A()
              {
                  // Example with qualifiers enclosing whitespace and delimiter, plus escaped qualifier
                  String s = "\"\"\"1\"\"\"";
                  // whitespace should be removed
                  var substrings = s.SplitQualified(',', '"', true);
                  CollectionAssert.AreEquivalent(new List<String> { "\"1\"" },
                                          substrings);
              }
          
          
              [TestMethod()]
              public void SplitQualified_15()
              {
                  // Instead of comma-delimited and quote-qualified, use pipe and hash
          
                  // Example with no whitespace or qualifiers
                  String s = "1|2|3|1|2,2f|3";
                  var substrings = s.SplitQualified('|', '#', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2", "3", "1", "2,2f", "3" }, substrings);
              }
              [TestMethod()]
              public void SplitQualified_16()
              {
                  // Instead of comma-delimited and quote-qualified, use pipe and hash
          
                  // Example with qualifiers enclosing whitespace and delimiter
                  String s = "# 1 #|#2 | 2b #|3|1|2|#  3|3c  #";
                  // whitespace should be removed
                  var substrings = s.SplitQualified('|', '#', true);
                  CollectionAssert.AreEquivalent(new List<String> { "1", "2 | 2b", "3", "1", "2", "3|3c" },
                                          substrings);
              }
              [TestMethod()]
              public void SplitQualified_17()
              {
                  // Instead of comma-delimited and quote-qualified, use pipe and hash
          
                  // Example with qualifiers enclosing whitespace and delimiter; also whitespace btwn delimiters
                  String s = "# 1 #| #2 | 2b #  |  3|1| 2 |#  3|3c  #";
                  // whitespace should be preserved
                  var substrings = s.SplitQualified('|', '#', false);
                  CollectionAssert.AreEquivalent(new List<String> { " 1 ", " 2 | 2b   ", "  3", "1", " 2 ", "  3|3c  " },
                                          substrings);
              }
          

          【讨论】:

            猜你喜欢
            相关资源
            最近更新 更多