Tạo danh sách được phân tách bằng dấu phẩy từ IList <string> hoặc IEnumerable <string>


851

Cách sạch nhất để tạo danh sách các giá trị chuỗi được phân tách bằng dấu phẩy từ một IList<string>hoặc là IEnumerable<string>gì?

String.Join(...)hoạt động trên một string[]vì vậy có thể cồng kềnh để làm việc khi các loại như IList<string>hoặc IEnumerable<string>không thể dễ dàng được chuyển đổi thành một chuỗi chuỗi.


5
Ôi ... rất tiếc. Tôi đã bỏ lỡ việc bổ sung phương thức mở rộng ToArray trong 3.5:public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
Daniel Fortunov

1
Nếu bạn đã đến câu hỏi này để tìm kiếm một phương tiện để viết CSV, thì bạn nên nhớ rằng chỉ cần chèn dấu phẩy giữa các mục là không đủ và sẽ gây ra lỗi trong trường hợp dấu ngoặc kép và dấu phẩy trong dữ liệu nguồn.
tiêu

Câu trả lời:


1448

.NET 4+

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

Chi tiết & Giải pháp Pre .Net 4.0

IEnumerable<string>có thể được chuyển đổi thành một chuỗi chuỗi rất dễ dàng với LINQ (.NET 3.5):

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

Thật dễ dàng để viết phương thức trợ giúp tương đương nếu bạn cần:

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

Sau đó gọi nó như thế này:

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

Sau đó bạn có thể gọi string.Join. Tất nhiên, bạn không phải sử dụng phương pháp trợ giúp:

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

Cái sau là một chút của một miệng mặc dù :)

Đây có thể là cách đơn giản nhất để thực hiện và cũng khá hiệu quả - có những câu hỏi khác về chính xác hiệu suất là như thế nào, bao gồm (nhưng không giới hạn) cái này .

Kể từ .NET 4.0, có nhiều tình trạng quá tải có sẵn string.Join, vì vậy bạn thực sự có thể viết:

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

Đơn giản hơn nhiều :)


Phương pháp trợ giúp liên quan đến việc tạo hai danh sách không cần thiết. Đây thực sự là cách tốt nhất để giải quyết vấn đề? Tại sao không tự ghép nó trong một vòng lặp foreach?
Eric

4
Phương thức của trình trợ giúp chỉ tạo một danh sách và một mảng. Vấn đề là kết quả cần phải là một mảng, không phải là một danh sách ... và bạn cần biết kích thước của một mảng trước khi bạn bắt đầu. Thực tiễn tốt nhất nói rằng bạn không nên liệt kê một nguồn nhiều lần trong LINQ trừ khi bạn phải - nó có thể đang làm tất cả các loại công cụ khó chịu. Vì vậy, bạn còn lại với việc đọc vào bộ đệm và thay đổi kích thước khi bạn đi - đó chính xác là những gì List<T>làm. Tại sao phải phát minh lại bánh xe?
Jon Skeet

9
Không, vấn đề là kết quả cần phải là một chuỗi nối. Không cần phải tạo một danh sách mới hoặc một mảng mới để đạt được mục tiêu này. Loại tâm lý .NET này làm tôi buồn.
Eric

38
Đó là nó. Mỗi câu trả lời đều dẫn đến Jon Skeet. Tôi sẽ truy cập var PurchBooks = AmazonContainer.Where (p => p.Author == "Jon Skeet"). Chọn ();
Zachary Scott

3
@ codeMonkey0110: Không có điểm nào có biểu thức truy vấn ở đó hoặc gọi ToList. Nó tốt để sử dụng string myStr = string.Join(",", foo.Select(a => a.someInt.ToString()))mặc dù.
Jon Skeet

179

FYI, phiên bản .NET 4.0 string.Join()có một số tình trạng quá tải , hoạt động với IEnumerablethay vì chỉ là mảng, bao gồm một mảng có thể xử lý bất kỳ loại nào T:

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

2
Điều này sẽ gọi phương thức T.ToString ()?
Philippe Lavoie

Chỉ là để bình luận điều này về câu trả lời của Jon. Cảm ơn đã đề cập.
Dan Bechard

2
Dù sao để làm điều này trên một tài sản của một đối tượng? (Ví dụ:
IEn Countable

1
Bạn phải chọn chuỗi đầu tiên, mặc dù bạn có thể tạo một phương thức mở rộng thực hiện điều đó. str = emps.Select(e => e.SSN).Join(",")
Xavier Poinas

65

Cách dễ nhất tôi có thể thấy để làm điều này là sử dụng Aggregatephương pháp LINQ :

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

20
Điều đó không chỉ phức tạp hơn (IMO) so với ToArray + Tham gia, nó cũng hơi kém hiệu quả - với chuỗi đầu vào lớn, điều đó sẽ bắt đầu hoạt động rất tệ.
Jon Skeet

35
Tuy nhiên, nó là đẹp nhất.
Merritt

2
Bạn có thể cung cấp tổng hợp một hạt StringBuilder, sau đó Func tổng hợp của bạn trở thành Func<StringBuilder,string,StringBuider>. Sau đó, chỉ cần gọi ToString()StringBuilder trả về. Tất nhiên là nó không đẹp như vậy :)
Matt Greer

3
Đây là cách rõ ràng nhất để làm những gì câu hỏi yêu cầu cho IMHO.
Derek Morrison

8
Coi chừng đó input.Countphải hơn 1.
Youngjae

31

Tôi nghĩ rằng cách sạch nhất để tạo danh sách các giá trị chuỗi được phân tách bằng dấu phẩy chỉ đơn giản là:

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

Dưới đây là một ví dụ đầy đủ:

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

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

Không cần thiết phải thực hiện chức năng trợ giúp, điều này được tích hợp vào .NET 4.0 trở lên.


4
Lưu ý rằng điều này có thể áp dụng bắt đầu với .NET 4 (như Xavier đã chỉ ra trong câu trả lời của anh ấy).
Derek Morrison

Từ quan điểm của người mới .NET 4 với kinh nghiệm chưa đầy một tháng, câu trả lời này là một sự kết hợp tốt đẹp của sự đúng đắn và cô đọng
Dexygen

13

So sánh bằng hiệu suất, người chiến thắng là "Lặp lại, sb. Hãy mở nó và thực hiện lại bước". Trên thực tế "vô số và di chuyển thủ công tiếp theo" là tốt như nhau (xem xét 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 |

Mã số:

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 đã được sử dụng


11

Vì tôi đã đến đây trong khi tìm kiếm để tham gia vào một thuộc tính cụ thể của danh sách các đối tượng (chứ không phải ToString () của nó) nên đây là một bổ sung cho câu trả lời được chấp nhận:

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

9

Đây là một phương pháp mở rộng khác:

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

8

Đến muộn một chút để thảo luận nhưng đây là fwiw đóng góp của tôi. Tôi có một IList<Guid> OrderIdschuyển đổi thành một chuỗi CSV nhưng sau đây là chung chung và hoạt động không được sửa đổi với các loại khác:

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

Ngắn gọn và ngọt ngào, sử dụng StringBuilder để xây dựng chuỗi mới, thu nhỏ độ dài StringBuilder một lần để xóa dấu phẩy cuối cùng và trả về chuỗi CSV.

Tôi đã cập nhật điều này để sử dụng nhiều Append()'s để thêm chuỗi + dấu phẩy. Từ phản hồi của James, tôi đã sử dụng Reflector để xem xét StringBuilder.AppendFormat(). Hóa ra AppendFormat()sử dụng StringBuilder để xây dựng chuỗi định dạng làm cho nó kém hiệu quả hơn trong ngữ cảnh này thay vì chỉ sử dụng nhiều Appends()'s.


Được thông báo, cảm ơn Xavier Tôi không biết về bản cập nhật đó trong .Net4. Dự án tôi đang thực hiện chưa thực hiện bước nhảy vọt nên tôi sẽ tiếp tục sử dụng ví dụ về người đi bộ hiện tại của mình trong thời gian này.
David Clarke

2
Điều này sẽ thất bại với nguồn IEnumerable không có mục. sb.Lipse-- cần kiểm tra giới hạn.
James Dunne

Rất vui được cảm ơn James, trong bối cảnh tôi đang sử dụng điều này, tôi được "đảm bảo" có ít nhất một OrderId. Tôi đã cập nhật cả ví dụ và mã của riêng tôi để bao gồm kiểm tra giới hạn (chỉ để chắc chắn).
David Clarke

@James Tôi nghĩ rằng gọi sb.Lipse-- một hack là một chút khắc nghiệt. Thực tế, tôi chỉ tránh bài kiểm tra "nếu (chưa)" của bạn cho đến khi kết thúc thay vì thực hiện trong mỗi lần lặp.
David Clarke

1
@James quan điểm của tôi là thường có nhiều hơn một câu trả lời đúng cho các câu hỏi được hỏi ở đây và coi một câu như là một "hack" ngụ ý nó không chính xác mà tôi sẽ tranh chấp. Đối với một số lượng nhỏ các hướng dẫn mà tôi đưa ra câu trả lời của Daniel ở trên có lẽ là hoàn toàn đầy đủ và nó chắc chắn ngắn gọn hơn / dễ đọc hơn câu trả lời của tôi. Tôi đang sử dụng điều này ở một nơi duy nhất trong mã của mình và tôi sẽ chỉ sử dụng dấu phẩy làm dấu phân cách. YAGNI nói rằng đừng xây dựng thứ gì đó bạn sẽ không cần. DRY được áp dụng nếu tôi cần thực hiện nhiều lần tại một thời điểm mà tôi sẽ tạo ra một phương pháp exension. HTH.
David Clarke

7

Một cái gì đó hơi khó hiểu, nhưng nó hoạt động:

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

Cung cấp cho bạn một CSV từ Danh sách sau khi bạn cung cấp cho nó trình chuyển đổi (trong trường hợp này là d => d.DivisionID.ToString ("b")).

Hacky nhưng hoạt động - có thể được thực hiện thành một phương pháp mở rộng có lẽ?


7

Đây là cách tôi đã làm, sử dụng cách tôi đã thực hiện bằng các ngôn ngữ khác:

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

Nhu cầu cụ thể khi chúng ta nên bao quanh bởi ', bởi 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()));

4

Chúng tôi có một chức năng tiện ích, một cái gì đó như thế này:

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

Có thể được sử dụng để tham gia nhiều bộ sưu tập một cách dễ dàng:

int[] ids = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233};

string csv = StringUtility.Join(",", ids, i => i.ToString() );

Lưu ý rằng chúng tôi có bộ sưu tập param trước lambda vì intellisense sau đó chọn loại bộ sưu tập.

Nếu bạn đã có một bảng liệt kê các chuỗi, tất cả những gì bạn cần làm là ToArray:

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

2
Tôi có một phương thức mở rộng thực hiện gần như chính xác điều tương tự, rất hữu ích: stackoverflow.com/questions/696850/
dọa

Vâng, bạn có thể viết điều này như một phương thức mở rộng .ToDelrictString đủ dễ dàng. Tôi sẽ đi với chuỗi đơn của mình. Tham gia một chuỗi thay vì sử dụng StringBuilder để cắt xén char cuối cùng.
Keith

3

bạn có thể chuyển đổi IList thành một mảng bằng ToArray và sau đó chạy lệnh string.join trên mảng.

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

3

Chúng có thể dễ dàng được chuyển đổi thành một mảng bằng cách sử dụng các phần mở rộng Linq trong .NET 3.5.

   var stringArray = stringList.ToArray();

3

Bạn cũng có thể sử dụng một cái gì đó như sau sau khi bạn đã chuyển đổi nó thành một mảng bằng một trong các phương thức được liệt kê bởi những người khác:

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

Chỉnh sửa: Đây là một ví dụ khác


3

Tôi chỉ giải quyết vấn đề này trước khi xảy ra trên bài viết này. Giải pháp của tôi đi một cái gì đó như dưới đây:

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

Được gọi là:

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

Tôi cũng có thể dễ dàng thể hiện như vậy và cũng sẽ hiệu quả hơn:

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

3

Câu trả lời của tôi giống như ở trên Giải pháp tổng hợp nhưng nên bớt nặng cuộc gọi vì không có cuộc gọi ủy nhiệm rõ ràng:

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

Tất nhiên, người ta có thể mở rộng chữ ký để độc lập phân định. Tôi thực sự không phải là người hâm mộ của cuộc gọi sb.Remove () và tôi muốn cấu trúc lại nó thành một vòng lặp thẳng trong một IEnumerable và sử dụng MoveNext () để xác định có nên viết dấu phẩy hay không. Tôi sẽ tìm hiểu và đăng giải pháp đó nếu tôi gặp nó.


Đây là những gì tôi muốn ban đầu:

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

Không yêu cầu lưu trữ mảng hoặc danh sách tạm thời và không yêu cầu StringBuilder Remove()hoặc Length--hack.

Trong thư viện khung của tôi, tôi đã thực hiện một vài biến thể cho chữ ký phương thức này, mọi sự kết hợp bao gồm delimitervà các convertertham số với việc sử dụng ","x.ToString()làm mặc định, tương ứng.


3

Hy vọng rằng đây là cách đơn giản nhất

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

3

Tôi đã đi qua cuộc thảo luận này trong khi tìm kiếm một phương thức C # tốt để tham gia các chuỗi giống như nó được thực hiện với phương thức MySql CONCAT_WS(). Phương thức này khác với string.Join()phương thức ở chỗ nó không thêm dấu phân cách nếu các chuỗi là NULL hoặc trống.

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

sẽ chỉ trả lại Lastnamenếu tên đầu tiên trống, trong khi

chuỗi.Join (",", strLastname, strFirstname)

sẽ trở lại strLastname + ", "trong trường hợp tương tự.

Muốn có hành vi đầu tiên, tôi đã viết các phương pháp sau:

    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

Tôi đã viết một vài phương pháp mở rộng để thực hiện theo cách hiệu quả:

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

Điều này phụ thuộc vào

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

        return s+delim+item;
    }

3
Sử dụng toán tử + để nối chuỗi không phải là tuyệt vời vì nó sẽ khiến một chuỗi mới được phân bổ mỗi lần. Hơn nữa, mặc dù StringBuilder có thể được truyền ngầm thành một chuỗi, nhưng việc đó thường xuyên (mỗi lần lặp trong vòng lặp của bạn) sẽ phần lớn đánh bại mục đích của việc xây dựng chuỗi.
Daniel Fortunov

2

Bạn có thể sử dụng .ToArray()trên ListsIEnumerables, sau đó sử dụng String.Join()như bạn muốn.


0

Nếu các chuỗi bạn muốn tham gia nằm trong Danh sách các đối tượng, thì bạn cũng có thể làm một cái gì đó như thế này:

var studentNames = string.Join(", ", students.Select(x => x.name));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.