从IList <string>或IEnumerable <string>创建逗号分隔的列表


848

IList<string>或创建以逗号分隔的字符串值列表的最干净方法是什么IEnumerable<string>

String.Join(...)string[]诸如IList<string>IEnumerable<string>无法轻易将其转换为字符串数组的类型时,对此类进行操作可能会很麻烦。


5
哦,哎呀。我错过了在3.5中添加ToArray扩展方法的方法:public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
Daniel Fortunov,09年

1
如果您遇到此问题,正在寻找一种编写CSV的方法,则值得记住的是,仅在项目之间插入逗号是不够的,并且如果在源数据中使用引号和逗号,则会导致失败。
支出者

Answers:


1446

.NET 4+

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

.Net 4.0之前的细节和解决方案

IEnumerable<string>可以被转换成一个字符串数组非常与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());

后者虽然有点a :)

这很可能是最简单的方法,而且性能也很高-关于性能到底是什么样,还有其他问题,包括(但不限于)这一问题

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

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

简单得多:)


辅助方法涉及创建两个不必要的列表。这真的是解决问题的最佳方法吗?为什么不自己在foreach循环中串联它呢?
埃里克

4
helper方法仅创建一个列表和一个数组。关键是结果必须是数组,而不是列表...,并且在开始之前您需要知道数组的大小。最佳实践表明,除非必须这样做,否则您不应该在LINQ中枚举多个源,否则可能会做各种令人讨厌的事情。因此,您只需要继续读取缓冲区并调整大小即可List<T>。为什么要重新发明轮子?
乔恩·斯基特

9
不,关键是结果需要是一个串联的字符串。无需创建新列表或新数组即可实现此目标。这种.NET心态使我感到难过。
Eric

38
而已。每个答案都会导致Jon Skeet。我只是要去var PurchaseBooks = AmazonContainer.Where(p => p.Author ==“ Jon Skeet”)。Select();
扎卡里·斯科特

3
@ codeMonkey0110:那里没有查询表达式或调用毫无意义ToList。很好用string myStr = string.Join(",", foo.Select(a => a.someInt.ToString()))
乔恩·斯基特

179

仅供参考,.NET 4.0版本string.Join()具有一些额外的重载,它们IEnumerable不仅可以处理数组,还可以处理数组,包括可以处理任何类型的数组T

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

2
这将调用T.ToString()方法吗?
菲利普·拉瓦

正要就乔恩的答案发表评论。感谢您的提及。
丹·贝查德

2
无论如何要在对象的属性上执行此操作?(例如:IEnumerable <Employee>,并且Employee对象具有字符串.SSN属性,并获得以逗号分隔的SSN列表。)
granadaCoder 2014年

1
您必须首先选择字符串,尽管您可以创建一个扩展方法来执行该操作。str = emps.Select(e => e.SSN).Join(",")
Xavier Poinas 2014年

65

我可以看到的最简单的方法是使用LINQ Aggregate方法:

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

20
这不仅比ToArray + Join更复杂(IMO),而且效率低下-输入序列很大,这将开始表现很差。
乔恩·斯基特

35
仍然是最漂亮的。
梅里特

2
您可以提供Aggregate StringBuilder种子,然后Aggregate Func变为Func<StringBuilder,string,StringBuider>。然后只需调用ToString()返回的StringBuilder。当然,它不是那么漂亮:)
Matt Greer 2010年

3
这是解决IMHO问题的最清晰方法。
德里克·莫里森

8
要注意的是input.Count应大于1
Youngjae

31

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

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

这是一个完整的示例:

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

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

.NET 4.0及更高版本内置了此功能。


4
请注意,这适用于.NET 4(如Xavier在其答案中指出的那样)。
德里克·莫里森

从不到一个月的经验的.NET 4新手的角度来看,此答案是正确性和简洁性的完美结合
Dexygen 2014年

13

通过性能比较获胜者是“抱住它,附加它,然后退一步”。实际上,“下一个可枚举和手动移动”是一样的好事(考虑到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使用


11

由于我在搜索连接对象列表的特定属性时到达了此处(而不是对象的ToString()),因此这里添加了已接受的答案:

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

9

这是另一种扩展方法:

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

8

到达讨论的时间有点晚,但这是我的贡献。我有一个IList<Guid> 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()


乱七八糟,谢谢Xavier,我不知道.Net4中的更新。我正在从事的项目尚未取得飞跃,因此在此期间,我将继续使用我现在的行人示例。
David Clarke 2010年

2
零项IEnumerable源将失败。sb.Length-需要进行边界检查。
James Dunne 2010年

非常感谢James,在我使用它的情况下,我“保证”至少拥有一个OrderId。我已经更新了示例和自己的代码,以包括边界检查(请确保)。
David Clarke 2010年

@James我认为叫sb.Length-骇客有点苛刻。实际上,我只是避免在结束之前进行“ if(notdone)”测试,而不是在每次迭代中都这样做。
David Clarke 2010年

1
@James我的观点是,这里提出的问题通常有不止一个正确答案,将一个问题称为“ hack”意味着我会提出异议。对于少量的指导,我将上面Daniel的答案串联起来可能就足够了,并且肯定比我的答案更简洁/可读。我只在代码中的一个地方使用它,而我永远都不会使用逗号作为定界符。YAGNI表示不要构建您不需要的东西。如果需要多次执行DRY,则可以使用DRY,此时我将创建一种扩展方法。HTH。
大卫·克拉克

7

有点笨拙,但是可以工作:

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

给您转换器后,从列表中提供CSV(在本例中为d => d.DivisionID.ToString(“ b”))。

哈克,但行得通-也许可以做成扩展方法?


7

这是我使用其他语言完成的方式:

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

7

当我们用',例如包围时的特殊需要:

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

4

我们有一个实用程序函数,如下所示:

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之前有集合参数,因为intellisense然后选择了集合类型。

如果您已经有了字符串枚举,则只需要做ToArray:

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

2
我有一个扩展方法,几乎​​可以完成相同的事情,非常有用:stackoverflow.com/questions/696850/…–
LukeH

是的,您可以很轻松地将其编写为.ToDelimitedString扩展方法。我会使用我的单行字符串。加入一个而不是使用StringBuilder修剪最后一个字符。
基思

3

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

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

3

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

   var stringArray = stringList.ToArray();

3

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

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

编辑: 是另一个例子


3

我只是在解决本文之前就解决了这个问题。我的解决方案如下所示:

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

3

我的答案类似于上面的聚合解决方案,但由于没有显式的委托调用,因此应减少调用栈的负担:

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上的正则循环,并使用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--帮闲需要。

在我的框架库中,我对该方法签名进行了一些更改,分别包含delimiter和的每个组合以及converter使用","x.ToString()作为默认值的参数。


3

希望这是最简单的方法

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

3

我在寻找一个好的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;
    }

2

我写了一些扩展方法以一种有效的方式做到这一点:

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

3
使用+运算符连接字符串不是很好,因为它将导致每次分配一个新字符串。此外,尽管可以将StringBuilder隐式转换为字符串,但频繁地这样做(循环的每次迭代)将大大不利于使用字符串生成器的目的。
Daniel Fortunov 09年

2

您可以.ToArray()Lists和上使用IEnumerables,然后String.Join()根据需要使用。


0

如果要连接的字符串在对象列表中,那么您也可以执行以下操作:

var studentNames = string.Join(", ", students.Select(x => x.name));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.