【问题标题】:Creating a comma separated list from IList<string> or IEnumerable<string>从 IList<string> 或 IEnumerable<string> 创建逗号分隔列表
【发布时间】:2010-10-22 09:59:42
【问题描述】:

IList&lt;string&gt;IEnumerable&lt;string&gt; 创建以逗号分隔的字符串值列表的最简洁方法是什么?

String.Join(...)string[] 上运行,因此当IList&lt;string&gt;IEnumerable&lt;string&gt; 等类型无法轻松转换为字符串数组时,使用起来会很麻烦。

【问题讨论】:

  • 哦...哎呀。我错过了 3.5 中添加的 ToArray 扩展方法:public static TSource[] ToArray&lt;TSource&gt;(this IEnumerable&lt;TSource&gt; source)
  • 如果您来这个问题是为了寻找一种编写 CSV 的方法,请记住,仅在项目之间插入逗号是不够的,并且在源数据中包含引号和逗号的情况下会导致失败.

标签: c# string


【解决方案1】:

.NET 4+

IList<string> strings = new List<string>{"1","2","testing"};
string joined = string.Join(",", strings);

.Net 4.0 解决方案的详细信息和之前的解决方案

IEnumerable&lt;string&gt; 可以通过 LINQ (.NET 3.5) 轻松转换为字符串数组非常

IEnumerable<string> strings = ...;
string[] array = strings.ToArray();

如果需要,编写等效的辅助方法很容易:

public static T[] ToArray(IEnumerable<T> source)
{
    return new List<T>(source).ToArray();
}

然后这样称呼它:

IEnumerable<string> strings = ...;
string[] array = Helpers.ToArray(strings);

然后您可以致电string.Join。当然,您必须使用辅助方法:

// C# 3 and .NET 3.5 way:
string joined = string.Join(",", strings.ToArray());
// C# 2 and .NET 2.0 way:
string joined = string.Join(",", new List<string>(strings).ToArray());

后者有点拗口 :)

这可能是最简单的方法,而且性能也非常好 - 关于性能究竟如何,还有其他问题,包括(但不限于)this one

从 .NET 4.0 开始,string.Join 中提供了更多重载,因此您实际上可以直接编写:

string joined = string.Join(",", strings);

简单得多:)

【讨论】:

  • 辅助方法涉及创建两个不必要的列表。这真的是解决问题的最好方法吗?为什么不在 foreach 循环中自己连接它?
  • 辅助方法只创建 one 列表和 one 数组。关键是结果需要是一个数组,而不是一个列表......并且您需要在开始之前知道数组的大小。最佳实践表明你不应该在 LINQ 中多次枚举一个源,除非你必须这样做——它可能会做各种令人讨厌的事情。因此,您可以随时读取缓冲区并调整大小 - 这正是 List&lt;T&gt; 所做的。为什么要重新发明轮子?
  • 不,关键是结果需要是一个串联的字符串。无需创建新列表或新数组即可实现此目标。这种 .NET 心态让我很难过。
  • 就是这样。每个答案都指向 Jon Skeet。我只是要 var PurchaseBooks = AmazonContainer.Where(p => p.Author == "Jon Skeet").Select();
  • @codeMonkey0110: 在那里有一个查询表达式或调用ToList 是没有意义的。不过可以使用string myStr = string.Join(",", foo.Select(a =&gt; a.someInt.ToString()))
【解决方案2】:

您可以在ListsIEnumerables 上使用.ToArray(),然后根据需要使用String.Join()

【讨论】:

    【解决方案3】:

    您可以使用 ToArray 将 IList 转换为数组,然后在数组上运行 string.join 命令。

    Dim strs As New List(Of String)
    Dim arr As Array
    arr = strs.ToArray
    

    【讨论】:

      【解决方案4】:

      我认为最简单的方法是使用 LINQ Aggregate 方法:

      string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
      

      【讨论】:

      • 这不仅比 ToArray + Join 更复杂 (IMO),而且效率也有些低 - 输入序列很大,开始表现会很差。
      • 不过,它是最漂亮的。
      • 您可以为 Aggregate 提供一个 StringBuilder 种子,然后您的 Aggregate Func 变为 Func&lt;StringBuilder,string,StringBuider&gt;。然后只需在返回的 StringBuilder 上调用 ToString()。它当然不那么漂亮:)
      • 这是执行问题要求恕我直言的最清晰方法。
      • 注意input.Count 应该大于1。
      【解决方案5】:

      可以使用 .NET 3.5 中的 Linq 扩展轻松将它们转换为数组。

         var stringArray = stringList.ToArray();
      

      【讨论】:

        【解决方案6】:

        我们有一个效用函数,像这样:

        public static string Join<T>( string delimiter, 
            IEnumerable<T> collection, Func<T, string> convert )
        {
            return string.Join( delimiter, 
                collection.Select( convert ).ToArray() );
        }
        

        可用于轻松加入大量收藏:

        int[] ids = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233};
        
        string csv = StringUtility.Join(",", ids, i => i.ToString() );
        

        请注意,我们在 lambda 之前有集合参数,因为智能感知随后会选择集合类型。

        如果你已经有一个字符串枚举,你需要做的就是 ToArray:

        string csv = string.Join( ",", myStrings.ToArray() );
        

        【讨论】:

        • 我有一个扩展方法几乎完全一样,非常有用:stackoverflow.com/questions/696850/…
        • 是的,您可以轻松地将其编写为 .ToDelimitedString 扩展方法。我会用我的单行字符串。加入一个而不是使用 StringBuilder 修剪最后一个字符。
        【解决方案7】:

        在使用其他人列出的方法之一将其转换为数组后,您还可以使用类似以下的内容:

        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.IO;
        using System.Net;
        using System.Configuration;
        
        namespace ConsoleApplication
        {
            class Program
            {
                static void Main(string[] args)
                {
                    CommaDelimitedStringCollection commaStr = new CommaDelimitedStringCollection();
                    string[] itemList = { "Test1", "Test2", "Test3" };
                    commaStr.AddRange(itemList);
                    Console.WriteLine(commaStr.ToString()); //Outputs Test1,Test2,Test3
                    Console.ReadLine();
                }
            }
        }
        

        编辑: Here 是另一个例子

        【讨论】:

          【解决方案8】:

          我编写了一些扩展方法来以一种高效的方式完成它:

              public static string JoinWithDelimiter(this IEnumerable<String> that, string delim) {
                  var sb = new StringBuilder();
                  foreach (var s in that) {
                      sb.AppendToList(s,delim);
                  }
          
                  return sb.ToString();
              }
          

          这取决于

              public static string AppendToList(this String s, string item, string delim) {
                  if (s.Length == 0) {
                      return item;
                  }
          
                  return s+delim+item;
              }
          

          【讨论】:

          • 使用 + 运算符连接字符串不是很好,因为它会导致每次分配一个新字符串。此外,尽管可以将 StringBuilder 隐式转换为字符串,但经常这样做(循环的每次迭代)将在很大程度上破坏拥有字符串构建器的目的。
          【解决方案9】:

          到这个讨论有点晚了,但这是我的贡献。我有一个 IList&lt;Guid&gt; OrderIds 要转换为 CSV 字符串,但以下是通用的,并且未经修改即可与其他类型一起使用:

          string csv = OrderIds.Aggregate(new StringBuilder(),
                       (sb, v) => sb.Append(v).Append(","),
                       sb => {if (0 < sb.Length) sb.Length--; return sb.ToString();});
          

          短小精悍,使用 StringBuilder 构造新字符串,将 StringBuilder 长度缩小 1 以删除最后一个逗号并返回 CSV 字符串。

          我已对此进行了更新,以使用多个 Append() 添加字符串 + 逗号。根据 James 的反馈,我使用 Reflector 查看了StringBuilder.AppendFormat()。结果 AppendFormat() 使用 StringBuilder 来构造格式字符串,这使得它在这种情况下的效率低于仅使用多个 Appends() 的。

          【讨论】:

          • Gazumped,谢谢 Xavier 我不知道 .Net4 中的更新。我正在进行的项目还没有实现飞跃,因此我将在此期间继续使用我现在的普通示例。
          • 这将因 IEnumerable 源为零项而失败。 sb.Length-- 需要边界检查。
          • 很好,谢谢詹姆斯,在我使用它的上下文中,我“保证”至少有一个 OrderId。我已经更新了示例和我自己的代码以包含边界检查(只是为了确定)。
          • @James 我认为调用 sb.Length-- hack 有点苛刻。实际上,我只是在结束之前避免您的“如果(未完成)”测试,而不是在每次迭代中都进行。
          • @James 我的意思是,对于这里提出的问题,通常有不止一个正确答案,并且将其中的一个称为“hack”意味着我会质疑这是不正确的。对于少数几个向导,我将丹尼尔上面的答案连接起来可能是完全足够的,而且它肯定比我的答案更简洁/可读。我只在我的代码中的一个地方使用它,而且我只会使用逗号作为分隔符。 YAGNI 说不要构建你不需要的东西。如果我需要不止一次这样做,DRY 是适用的,此时我将创建一个扩展方法。 HTH。
          【解决方案10】:

          仅供参考,.NET 4.0 版本的string.Join() 有一些extra overloads,可以与IEnumerable 一起使用,而不仅仅是数组,包括可以处理任何类型T 的数组:

          public static string Join(string separator, IEnumerable<string> values)
          public static string Join<T>(string separator, IEnumerable<T> values)
          

          【讨论】:

          • 这将调用 T.ToString() 方法?
          • 正要对乔恩的回答发表评论。感谢提及。
          • 无论如何要对对象的属性执行此操作? (例如:IEnumerable 并且 Employee 对象上有一个字符串 .SSN 属性,并获得一个逗号分隔的 SSN 列表。)
          • 您必须先选择字符串,尽管您可以创建一个扩展方法来执行此操作。 str = emps.Select(e =&gt; e.SSN).Join(",")
          【解决方案11】:

          这是另一种扩展方法:

              public static string Join(this IEnumerable<string> source, string separator)
              {
                  return string.Join(separator, source);
              }
          

          【讨论】:

            【解决方案12】:

            我的回答类似于上面的聚合解决方案,但由于没有明确的委托调用,因此调用堆栈的重量应该更少:

            public static string ToCommaDelimitedString<T>(this IEnumerable<T> items)
            {
                StringBuilder sb = new StringBuilder();
                foreach (var item in items)
                {
                    sb.Append(item.ToString());
                    sb.Append(',');
                }
                if (sb.Length >= 1) sb.Length--;
                return sb.ToString();
            }
            

            当然,可以将签名扩展为与分隔符无关。我真的不喜欢 sb.Remove() 调用,我想将它重构为 IEnumerable 上的直接 while 循环,并使用 MoveNext() 来确定是否写逗号。如果我遇到它,我会摆弄并发布该解决方案。


            这是我最初想要的:

            public static string ToDelimitedString<T>(this IEnumerable<T> source, string delimiter, Func<T, string> converter)
            {
                StringBuilder sb = new StringBuilder();
                var en = source.GetEnumerator();
                bool notdone = en.MoveNext();
                while (notdone)
                {
                    sb.Append(converter(en.Current));
                    notdone = en.MoveNext();
                    if (notdone) sb.Append(delimiter);
                }
                return sb.ToString();
            }
            

            不需要临时数组或列表存储,也不需要 StringBuilder Remove()Length-- hack。

            在我的框架库中,我对该方法签名进行了一些变体,包括delimiterconverter 参数的每个组合,分别使用","x.ToString() 作为默认值。

            【讨论】:

              【解决方案13】:

              有点难看,但确实有效:

              string divisionsCSV = String.Join(",", ((List<IDivisionView>)divisions).ConvertAll<string>(d => d.DivisionID.ToString("b")).ToArray());
              

              在你给它一个转换器后,给你一个来自列表的 CSV(在本例中为 d => d.DivisionID.ToString("b"))。

              Hacky 但有效 - 也许可以做成扩展方法?

              【讨论】:

                【解决方案14】:

                这是我的做法,使用我在其他语言中的做法:

                private string ToStringList<T>(IEnumerable<T> list, string delimiter)
                {
                  var sb = new StringBuilder();
                  string separator = String.Empty;
                  foreach (T value in list)
                  {
                    sb.Append(separator).Append(value);
                    separator = delimiter;
                  }
                  return sb.ToString();
                }
                

                【讨论】:

                  【解决方案15】:

                  我认为创建以逗号分隔的字符串值列表的最简洁方法是:

                  string.Join<string>(",", stringEnumerable);
                  

                  这是一个完整的例子:

                  IEnumerable<string> stringEnumerable= new List<string>();
                  stringList.Add("Comma");
                  stringList.Add("Separated");
                  
                  string.Join<string>(",", stringEnumerable);
                  

                  无需制作辅助函数,.NET 4.0及以上版本内置。

                  【讨论】:

                  • 请注意,这适用于从 .NET 4 开始(正如 Xavier 在他的回答中指出的那样)。
                  • 从不到一个月经验的 .NET 4 新手的角度来看,这个答案是正确性和简洁性的完美结合
                  【解决方案16】:

                  希望这是最简单的方法

                   string Commaseplist;
                   string[] itemList = { "Test1", "Test2", "Test3" };
                   Commaseplist = string.join(",",itemList);
                   Console.WriteLine(Commaseplist); //Outputs Test1,Test2,Test3
                  

                  【讨论】:

                    【解决方案17】:

                    在这篇文章发生之前,我刚刚解决了这个问题。我的解决方案如下:

                       private static string GetSeparator<T>(IList<T> list, T item)
                       {
                           return (list.IndexOf(item) == list.Count - 1) ? "" : ", ";
                       }
                    

                    调用如下:

                    List<thing> myThings;
                    string tidyString;
                    
                    foreach (var thing in myThings)
                    {
                         tidyString += string.format("Thing {0} is a {1}", thing.id, thing.name) + GetSeparator(myThings, thing);
                    }
                    

                    我也可以这样简单地表达,而且效率也会更高:

                    string.Join(“,”, myThings.Select(t => string.format(“Thing {0} is a {1}”, t.id, t.name)); 
                    

                    【讨论】:

                      【解决方案18】:

                      具体需要什么时候用', by ex:

                              string[] arr = { "jj", "laa", "123" };
                              List<string> myList = arr.ToList();
                      
                              // 'jj', 'laa', '123'
                              Console.WriteLine(string.Join(", ",
                                  myList.ConvertAll(m =>
                                      string.Format("'{0}'", m)).ToArray()));
                      

                      【讨论】:

                        【解决方案19】:

                        我在寻找一个好的 C# 方法来连接字符串时遇到了这个讨论,就像使用 MySql 方法 CONCAT_WS() 完成的那样。此方法与 string.Join() 方法的不同之处在于,如果字符串为 NULL 或空,它不会添加分隔符。

                        CONCAT_WS(', ',tbl.Lastname,tbl.Firstname)

                        如果名字为空,将只返回Lastname,而

                        string.Join(", ", strLastname, strFirstname)

                        将在相同的情况下返回strLastname + ", "

                        想要第一个行为,我写了以下方法:

                            public static string JoinStringsIfNotNullOrEmpty(string strSeparator, string strA, string strB, string strC = "")
                            {
                                return JoinStringsIfNotNullOrEmpty(strSeparator, new[] {strA, strB, strC});
                            }
                        
                            public static string JoinStringsIfNotNullOrEmpty(string strSeparator, string[] arrayStrings)
                            {
                                if (strSeparator == null)
                                    strSeparator = "";
                                if (arrayStrings == null)
                                    return "";
                                string strRetVal = arrayStrings.Where(str => !string.IsNullOrEmpty(str)).Aggregate("", (current, str) => current + (str + strSeparator));
                                int trimEndStartIndex = strRetVal.Length - strSeparator.Length;
                                if (trimEndStartIndex>0)
                                    strRetVal = strRetVal.Remove(trimEndStartIndex);
                                return strRetVal;
                            }
                        

                        【讨论】:

                        • 替代:string separator = ", "; string strA = "High"; string strB = ""; string strC = "Five"; string strD = null; var myStrings = new List&lt;string&gt; { strA, strB, strC, strD }; IEnumerable&lt;string&gt; myValidStrings = myStrings.Where(item =&gt; !string.IsNullOrWhiteSpace(item)); return string.Join(separator, myValidStrings );
                        【解决方案20】:

                        由于我在搜索加入对象列表的特定属性(而不是它的 ToString())时到达这里,所以这里是对已接受答案的补充:

                        var commaDelimited = string.Join(",", students.Where(i => i.Category == studentCategory)
                                                         .Select(i => i.FirstName));
                        

                        【讨论】:

                        • 每次我需要这样做时,我认为“我真的应该花几分钟来弄清楚如何使用 string.Join(...) 在一行中做到这一点”但后来我最终只是 foreach-ing 它并继续前进。感谢您发布此信息! :)
                        【解决方案21】:

                        按性能比较获胜者是“循环它,sb.Append 它,然后退一步”。 实际上“可枚举和手动下一步移动”同样好(考虑 stddev)。

                        BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393
                        Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
                        Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC
                          [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0
                          Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0
                          Core   : .NET Core 4.6.25009.03, 64bit RyuJIT
                        
                        
                                        Method |  Job | Runtime |     Mean |     Error |    StdDev |      Min |      Max |   Median | Rank |  Gen 0 | Allocated |
                        ---------------------- |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
                                    StringJoin |  Clr |     Clr | 28.24 us | 0.4381 us | 0.3659 us | 27.68 us | 29.10 us | 28.21 us |    8 | 4.9969 |   16.3 kB |
                         SeparatorSubstitution |  Clr |     Clr | 17.90 us | 0.2900 us | 0.2712 us | 17.55 us | 18.37 us | 17.80 us |    6 | 4.9296 |  16.27 kB |
                             SeparatorStepBack |  Clr |     Clr | 16.81 us | 0.1289 us | 0.1206 us | 16.64 us | 17.05 us | 16.81 us |    2 | 4.9459 |  16.27 kB |
                                    Enumerable |  Clr |     Clr | 17.27 us | 0.0736 us | 0.0615 us | 17.17 us | 17.36 us | 17.29 us |    4 | 4.9377 |  16.27 kB |
                                    StringJoin | Core |    Core | 27.51 us | 0.5340 us | 0.4995 us | 26.80 us | 28.25 us | 27.51 us |    7 | 5.0296 |  16.26 kB |
                         SeparatorSubstitution | Core |    Core | 17.37 us | 0.1664 us | 0.1557 us | 17.15 us | 17.68 us | 17.39 us |    5 | 4.9622 |  16.22 kB |
                             SeparatorStepBack | Core |    Core | 15.65 us | 0.1545 us | 0.1290 us | 15.45 us | 15.82 us | 15.66 us |    1 | 4.9622 |  16.22 kB |
                                    Enumerable | Core |    Core | 17.00 us | 0.0905 us | 0.0654 us | 16.93 us | 17.12 us | 16.98 us |    3 | 4.9622 |  16.22 kB |
                        

                        代码:

                        public class BenchmarkStringUnion
                        {
                            List<string> testData = new List<string>();
                            public BenchmarkStringUnion()
                            {
                                for(int i=0;i<1000;i++)
                                {
                                    testData.Add(i.ToString());
                                }
                            }
                            [Benchmark]
                            public string StringJoin()
                            {
                                var text = string.Join<string>(",", testData);
                                return text;
                            }
                            [Benchmark]
                            public string SeparatorSubstitution()
                            {
                                var sb = new StringBuilder();
                                var separator = String.Empty;
                                foreach (var value in testData)
                                {
                                    sb.Append(separator).Append(value);
                                    separator = ",";
                                }
                                return sb.ToString();
                            }
                        
                            [Benchmark]
                            public string SeparatorStepBack()
                            {
                                var sb = new StringBuilder();
                                foreach (var item in testData)
                                    sb.Append(item).Append(',');
                                if (sb.Length>=1) 
                                    sb.Length--;
                                return sb.ToString();
                            }
                        
                            [Benchmark]
                            public string Enumerable()
                            {
                                var sb = new StringBuilder();
                                var e = testData.GetEnumerator();
                                bool  moveNext = e.MoveNext();
                                while (moveNext)
                                {
                                    sb.Append(e.Current);
                                    moveNext = e.MoveNext();
                                    if (moveNext) 
                                        sb.Append(",");
                                }
                                return sb.ToString();
                            }
                        }
                        

                        使用了https://github.com/dotnet/BenchmarkDotNet

                        【讨论】:

                          【解决方案22】:

                          如果您要加入的字符串在对象列表中,那么您也可以这样做:

                          var studentNames = string.Join(", ", students.Select(x => x.name));
                          

                          【讨论】:

                            【解决方案23】:

                            要从IList&lt;string&gt;IEnumerable&lt;string&gt; 创建逗号分隔列表,除了使用string.Join() 之外,您还可以使用StringBuilder.AppendJoin 方法:

                            new StringBuilder().AppendJoin(", ", itemList).ToString();
                            

                            $"{new StringBuilder().AppendJoin(", ", itemList)}";
                            

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 2022-11-12
                              • 2016-07-23
                              • 2021-01-04
                              • 2011-11-20
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 2020-12-12
                              相关资源
                              最近更新 更多