Membuat daftar yang dipisahkan koma dari IList <string> atau IEnumerable <string>


851

Apa cara paling bersih untuk membuat daftar nilai string yang dipisahkan koma dari IList<string>atau IEnumerable<string>?

String.Join(...)beroperasi pada string[]sehingga bisa rumit untuk bekerja dengan ketika tipe seperti IList<string>atau IEnumerable<string>tidak dapat dengan mudah dikonversi menjadi string array.


5
Oh ... wah. Saya melewatkan penambahan metode ekstensi ToArray di 3,5:public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
Daniel Fortunov

1
Jika Anda datang ke pertanyaan ini mencari cara untuk menulis CSV, perlu diingat bahwa hanya memasukkan koma di antara item tidak cukup dan akan menyebabkan kegagalan dalam hal kutipan dan koma dalam data sumber.
pemboros

Jawaban:


1448

.NET 4+

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

Detail & Pra. Net 4.0 Solusi

IEnumerable<string>dapat dikonversi menjadi larik string dengan sangat mudah dengan LINQ (.NET 3.5):

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

Cukup mudah untuk menulis metode pembantu yang setara jika Anda perlu:

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

Kemudian panggil seperti ini:

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

Anda kemudian dapat menelepon string.Join. Tentu saja, Anda tidak harus menggunakan metode pembantu:

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

Yang terakhir ini sedikit suap :)

Ini mungkin menjadi cara paling sederhana untuk melakukannya, dan cukup berkinerja baik - ada pertanyaan lain tentang seperti apa performanya, termasuk (tapi tidak terbatas pada) yang ini .

Pada. NET 4.0, ada lebih banyak kelebihan yang tersedia di string.Join, sehingga Anda sebenarnya bisa menulis:

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

Jauh lebih sederhana :)


Metode helper melibatkan pembuatan dua daftar yang tidak perlu. Apakah ini benar-benar cara terbaik untuk menyelesaikan masalah? Mengapa tidak menyatukan sendiri dalam loop foreach?
Eric

4
Metode helper hanya membuat satu daftar dan satu array. Intinya adalah bahwa hasilnya harus berupa array, bukan daftar ... dan Anda perlu mengetahui ukuran array sebelum Anda mulai. Praktik terbaik mengatakan Anda tidak perlu menyebutkan sumber lebih dari satu kali di LINQ kecuali Anda harus - mungkin melakukan semua jenis hal-hal buruk. Jadi, Anda dibiarkan membaca buffer dan mengubah ukuran saat Anda pergi - dan itulah yang List<T>sebenarnya. Mengapa menemukan kembali roda?
Jon Skeet

9
Tidak, intinya adalah bahwa hasilnya harus berupa string gabungan. Tidak perlu membuat daftar baru atau array baru untuk mencapai tujuan ini. Mentalitas .NET seperti ini membuat saya sedih.
Eric

38
Itu dia. Setiap jawaban mengarah ke Jon Skeet. Saya hanya akan mengunjungi PurchaseBooks = AmazonContainer.Where (p => p.Author == "Jon Skeet"). Pilih ();
Zachary Scott

3
@ codeMonkey0110: Ya tidak ada gunanya memiliki ekspresi permintaan di sana, atau menelepon ToList. Tidak apa-apa untuk digunakan string myStr = string.Join(",", foo.Select(a => a.someInt.ToString())).
Jon Skeet

179

FYI, versi .NET 4.0 dari string.Join()memiliki beberapa kelebihan , yang bekerja dengan IEnumerablebukan hanya array, termasuk yang dapat menangani semua jenis T:

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

2
Ini akan memanggil metode T.ToString ()?
Philippe Lavoie

Baru saja akan mengomentari ini pada jawaban Jon. Terima kasih telah menyebutkan.
Dan Bechard

2
Lagi pula untuk melakukan ini pada properti objek? (Mis: IEnumerable <Employee> dan objek Employee memiliki string .SSN properti di atasnya, dan mendapatkan daftar SSN yang dipisahkan koma.)
granadaCoder

1
Anda harus memilih string terlebih dahulu, meskipun Anda dapat membuat metode ekstensi yang melakukan itu. str = emps.Select(e => e.SSN).Join(",")
Xavier Poinas

65

Cara termudah yang dapat saya lihat untuk melakukan ini adalah menggunakan Aggregatemetode LINQ :

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

20
Itu tidak hanya lebih rumit (IMO) daripada ToArray + Bergabung, ini juga agak tidak efisien - dengan urutan input yang besar, itu akan mulai berkinerja sangat buruk.
Jon Skeet

35
Tetap saja, ini yang paling cantik.
Merritt

2
Anda bisa memberi makan benih StringBuilder dari Agregat, kemudian Fungsi Agregat Anda menjadi Func<StringBuilder,string,StringBuider>. Kemudian panggil ToString()StringBuilder yang dikembalikan. Ini tentu saja tidak secantik :)
Matt Greer

3
Ini adalah cara paling jelas untuk melakukan apa yang ditanyakan oleh IMHO.
Derek Morrison

8
Waspadalah yang input.Countseharusnya lebih dari 1.
Youngjae

31

Saya pikir cara paling bersih untuk membuat daftar nilai string yang dipisahkan koma adalah:

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

Ini adalah contoh lengkapnya:

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

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

Tidak perlu membuat fungsi pembantu, ini dibangun ke dalam. NET 4.0 dan di atas.


4
Perhatikan bahwa ini berlaku mulai dengan .NET 4 (seperti yang ditunjukkan Xavier dalam jawabannya).
Derek Morrison

Dari sudut pandang pemula .NET 4 dengan pengalaman kurang dari sebulan jawaban ini adalah kombinasi yang bagus antara kebenaran dan
kesederhanaan

13

Membandingkan berdasarkan kinerja, pemenangnya adalah "Ulangi, sb. Tambahkan, dan lakukan langkah mundur". Sebenarnya "enumerable dan manual move next" adalah barang yang sama (pertimbangkan 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 |

Kode:

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 digunakan


11

Karena saya sampai di sini ketika mencari untuk bergabung pada properti tertentu dari daftar objek (dan bukan ToString ()), berikut ini adalah tambahan untuk jawaban yang diterima:

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

9

Berikut metode ekstensi lain:

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

8

Tiba sedikit terlambat untuk diskusi ini, tetapi ini adalah kontribusi saya fwiw. Saya harus IList<Guid> OrderIdsmengkonversikannya ke string CSV tetapi yang berikut bersifat generik dan berfungsi tidak dimodifikasi dengan tipe lain:

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

Pendek dan manis, menggunakan StringBuilder untuk membuat string baru, menyusutkan panjang StringBuilder satu per satu untuk menghilangkan koma terakhir dan mengembalikan string CSV.

Saya telah memperbarui ini untuk menggunakan banyak Append()untuk menambahkan string + koma. Dari umpan balik James, saya menggunakan Reflector untuk melihatnya StringBuilder.AppendFormat(). Ternyata AppendFormat()menggunakan StringBuilder untuk membangun string format yang membuatnya kurang efisien dalam konteks ini daripada hanya menggunakan banyak Appends().


Gazumped, terima kasih Xavier saya tidak mengetahui pembaruan itu di .Net4. Proyek yang sedang saya kerjakan belum membuat lompatan jadi saya akan tetap menggunakan contoh pejalan kaki saya yang sekarang.
David Clarke

2
Ini akan gagal dengan sumber IEnumerable nol-item. sb. Panjang - perlu pemeriksaan batas.
James Dunne

Tangkapan yang bagus terima kasih James, dalam konteks di mana saya menggunakan ini, saya "dijamin" memiliki setidaknya satu OrderId. Saya telah memperbarui contoh dan kode saya sendiri untuk memasukkan cek batas (hanya untuk memastikan).
David Clarke

@ James, saya pikir memanggil sb.Length-- hack sedikit keras. Secara efektif saya hanya menghindari tes "jika (tidak dilakukan)" Anda sampai akhir daripada melakukannya di setiap iterasi.
David Clarke

1
@James maksud saya adalah sering ada lebih dari satu jawaban yang benar untuk pertanyaan yang diajukan di sini dan merujuknya sebagai "retasan" menyiratkan bahwa tidak benar yang akan saya sengketakan. Untuk sejumlah kecil panduan yang saya gabungkan jawaban Daniel di atas mungkin akan cukup memadai dan tentu saja lebih ringkas / mudah dibaca daripada jawaban saya. Saya menggunakan ini hanya di satu tempat dalam kode saya dan saya hanya akan menggunakan koma sebagai pembatas. YAGNI mengatakan jangan membangun sesuatu yang tidak Anda butuhkan. KERING berlaku jika saya perlu melakukannya lebih dari satu kali di mana saya akan membuat metode exension. HTH.
David Clarke

7

Sesuatu yang agak aneh, tetapi berhasil:

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

Memberi Anda CSV dari Daftar setelah Anda memberikan konverter (dalam hal ini d => d.DivisionID.ToString ("b")).

Hacky tetapi berhasil - mungkin bisa dibuat menjadi metode ekstensi?


7

Inilah cara saya melakukannya, menggunakan cara saya melakukannya dalam bahasa lain:

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

Kebutuhan khusus ketika kita harus dikelilingi oleh ', oleh 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

Kami memiliki fungsi utilitas, sesuatu seperti ini:

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

Yang dapat digunakan untuk bergabung dengan banyak koleksi dengan mudah:

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

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

Perhatikan bahwa kita memiliki param koleksi sebelum lambda karena intellisense kemudian mengambil jenis koleksi.

Jika Anda sudah memiliki enumerasi string, yang perlu Anda lakukan adalah ToArray:

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

2
Saya memiliki sebuah metode ekstensi yang tidak hampir persis hal yang sama, yang sangat berguna: stackoverflow.com/questions/696850/...
LukeH

Ya, Anda bisa menulis ini sebagai metode ekstensi .ToDelimitedString dengan cukup mudah. Saya akan pergi dengan string baris tunggal saya. Bergabung satu daripada menggunakan StringBuilder pemangkasan char terakhir.
Keith

3

Anda bisa mengonversi IList ke array menggunakan ToArray dan kemudian menjalankan perintah string.join pada array.

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

3

Mereka dapat dengan mudah dikonversi ke array menggunakan ekstensi Linq di .NET 3.5.

   var stringArray = stringList.ToArray();

3

Anda juga bisa menggunakan sesuatu seperti berikut ini setelah Anda mengonversinya menjadi array menggunakan salah satu metode yang terdaftar oleh orang lain:

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

Sunting: Ini adalah contoh lain


3

Saya baru saja memecahkan masalah ini sebelum terjadi di artikel ini. Solusi saya berjalan seperti di bawah ini:

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

Disebut seperti:

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

Saya juga bisa dengan mudah diungkapkan seperti itu dan juga akan lebih efisien:

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

3

Jawaban saya adalah seperti solusi Agregat di atas tetapi harus lebih sedikit tumpukan panggilan karena tidak ada panggilan delegasi eksplisit:

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

Tentu saja, seseorang dapat memperluas tanda tangan untuk menjadi pembatas-independen. Saya benar-benar bukan penggemar panggilan sb.Remove () dan saya ingin refactor menjadi straight-up while-loop melalui IEnumerable dan menggunakan MoveNext () untuk menentukan apakah akan menulis koma atau tidak. Saya akan mengutak-atik dan memposting solusi itu jika saya menemukannya.


Inilah yang saya inginkan pada awalnya:

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

Tidak diperlukan penyimpanan sementara array atau daftar dan tidak ada StringBuilder Remove()atau Length--hack diperlukan.

Di perpustakaan kerangka kerja saya, saya membuat beberapa variasi pada metode tanda tangan ini, setiap kombinasi termasuk delimiterdan converterparameter dengan penggunaan ","dan x.ToString()sebagai default, masing-masing.


3

Semoga ini adalah cara paling sederhana

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

3

Saya datang ke diskusi ini sambil mencari metode C # yang baik untuk bergabung dengan string seperti yang dilakukan dengan metode MySql CONCAT_WS(). Metode ini berbeda dari string.Join()metode yang tidak menambahkan tanda pemisah jika string NULL atau kosong.

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

akan kembali hanya Lastnamejika nama depan kosong, sementara

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

akan kembali strLastname + ", "dalam kasus yang sama.

Menginginkan perilaku pertama, saya menulis metode berikut:

    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

Saya menulis beberapa metode ekstensi untuk melakukannya dengan cara yang efisien:

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

Ini tergantung pada

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

        return s+delim+item;
    }

3
Menggunakan operator + untuk menggabungkan string tidak bagus karena akan menyebabkan string baru dialokasikan setiap kali. Lebih jauh lagi, meskipun StringBuilder dapat secara implisit dilemparkan ke sebuah string, melakukan hal itu dengan sering (setiap iterasi dari loop Anda) sebagian besar akan mengalahkan tujuan memiliki pembangun string.
Daniel Fortunov

2

Anda dapat menggunakan .ToArray()pada Listsdan IEnumerables, dan kemudian menggunakan String.Join()seperti yang Anda inginkan.


0

Jika string yang ingin Anda gabungkan ada di Daftar Objek, maka Anda dapat melakukan sesuatu seperti ini juga:

var studentNames = string.Join(", ", students.Select(x => x.name));
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.