IList <string> veya IEnumerable <string> öğesinden virgülle ayrılmış bir liste oluşturma


848

Bir dize değerleri virgülle ayrılmış listesini oluşturmak için en temiz yolu nedir IList<string>ya IEnumerable<string>?

String.Join(...)gibi çalışır bir string[]tür dizi dizisine kolayca dönüştürülebilir IList<string>veya IEnumerable<string>dönüştürülemez zaman ile çalışmak için hantal olabilir.


5
Hata! 3.5'te ToArray eklenti yönteminin eklenmesini kaçırdım:public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
Daniel Fortunov

1
CSV yazmanın bir yolunu bulmak için bu soruya geldiyseniz, öğeler arasına virgül eklemenin yeterli olmadığını ve kaynak verilerdeki tırnak ve virgüllerde başarısızlığa neden olacağını hatırlamakta fayda var.
harcama

Yanıtlar:


1446

.NET 4+

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

Detay ve Pre .Net 4.0 Çözümleri

IEnumerable<string>LINQ (.NET 3.5) ile çok kolay bir şekilde dizi dizisine dönüştürülebilir :

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

Gerekirse, eşdeğer yardımcı yöntemi yazmak yeterince kolaydır:

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

Sonra şöyle deyin:

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

Daha sonra arayabilirsiniz string.Join. Tabii ki, yok olması bir yardımcı yöntemi kullanmak:

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

İkincisi olsa bir ağız dolusu biraz :)

Hakkında diğer sorular vardır performans dahil (ancak bunlarla sınırlı olmamak üzere) gibi tam olarak ne - Bu oldukça ölçülebilir de onu yapmanın en kolay yolu olması muhtemeldir ve bu bir .

.NET 4.0'dan itibaren, daha fazla aşırı yük var string.Join, bu yüzden sadece şunu yazabilirsiniz:

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

Çok daha basit :)


Yardımcı yöntem, iki gereksiz liste oluşturmayı içerir. Bu gerçekten sorunu çözmenin en iyi yolu mu? Neden bir foreach döngüsünde kendiniz bitiştirmiyorsunuz?
Eric

4
Helper yöntemi yalnızca bir liste ve bir dizi oluşturur. Önemli olan, sonucun bir liste değil, bir dizi olması gerektiğidir ... ve başlamadan önce bir dizinin boyutunu bilmeniz gerekir. En iyi uygulama, gerekmedikçe LINQ'da bir kaynağı bir kereden fazla numaralandırmamanız gerektiğini söylüyor - her türlü kötü şey yapıyor olabilir. Yani, arabelleklere okuma ve gittikçe yeniden boyutlandırma ile kalıyorsunuz - ki tam olarak ne List<T>var. Neden tekerleği yeniden icat ettiniz?
Jon Skeet

9
Hayır, önemli olan, sonucun birleştirilmiş bir dize olması gerektiğidir. Bu hedefe ulaşmak için yeni bir liste veya yeni bir dizi oluşturmaya gerek yoktur. Bu tür bir .NET zihniyeti beni üzüyor.
Eric

38
Bu kadar. Her cevap Jon Skeet'e götürür. Ben sadece var BuyseBooks = AmazonContainer.Where (p => p.Author == "Jon Skeet").
Zachary Scott

3
@ codeMonkey0110: Orada bir sorgu ifadesine sahip olmanın veya çağırmanın bir anlamı yok ToList. Yine de kullanmak iyidir string myStr = string.Join(",", foo.Select(a => a.someInt.ToString())).
Jon Skeet

179

FYI, .NET 4.0 sürümü, herhangi bir türle başa çıkabilenler de dahil olmak üzere, yalnızca diziler yerine çalışan string.Join()bazı ekstra aşırı yüklemelere sahiptir :IEnumerableT

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

2
Bu T.ToString () yöntemini çağıracak mı?
Philippe Lavoie

Jon'un cevabına bunu yorumlamak üzereydi. Bahsettiğiniz için teşekkürler.
Dan Bechard

2
Neyse bunu bir nesnenin bir özelliği üzerinde yapmak için? (Örn: IEnumerable <Employee> ve Employee nesnesinin üzerinde dize .SSN özelliği var ve SSN'lerin virgülle ayrılmış bir listesi var.)
granadaCoder

1
İlk önce dizeyi seçmelisiniz, ancak bunu yapan bir uzantı yöntemi oluşturabilirsiniz. str = emps.Select(e => e.SSN).Join(",")
Xavier Poinas

65

Bunu yapmanın en kolay yolu LINQ Aggregateyöntemini kullanmaktır:

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

20
Bu sadece ToArray + Join'den daha karmaşık (IMO) değil, aynı zamanda biraz verimsiz - büyük bir giriş sırası ile, çok kötü performans göstermeye başlayacak.
Jon Skeet

35
Yine de, en güzel.
Merritt

2
Toplama bir StringBuilder tohum besleyebilirsiniz, sonra Toplama Func olur Func<StringBuilder,string,StringBuider>. Sonra sadece ToString()döndürülen StringBuilder'ı arayın . Tabii ki o kadar da güzel değil :)
Matt Greer

3
Bu, IMHO için sorulan soruyu yapmanın en açık yoludur.
Derek Morrison

8
input.Count1'den fazla olması gerektiğine dikkat edin .
Youngjae

31

Dize değerleri virgülle ayrılmış bir liste oluşturmak için en temiz yolu basitçe olduğunu düşünüyorum:

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

İşte tam bir örnek:

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

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

Yardımcı bir işlev yapmaya gerek yoktur, bu .NET 4.0 ve üstü sürümlerde yerleşiktir.


4
Bunun .NET 4'ten başlayarak geçerli olduğunu unutmayın (Xavier'in cevabında belirttiği gibi).
Derek Morrison

Bir aydan daha az deneyime sahip .NET 4 newbie bakış açısından bu cevap, doğruluk ve
özlülüğün

13

Performansa göre kazanan "Dön, sb. Ekle ve geri adım at". Aslında "sıralanabilir ve manuel hareket" aynı şeydir (stddev'i düşünün).

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 |

Kod:

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 kullanıldı


11

Burada bir nesne listesinin (ve ToString () değil) belirli bir özelliğine katılmak için arama yaparken ulaştığımdan beri, kabul edilen cevaba bir ek:

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

9

İşte başka bir uzantı yöntemi:

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

8

Bu tartışmaya biraz geç geliyorum ama bu benim katkım. Ben IList<Guid> OrderIdsbir CSV dizeye dönüştürülecek var ama aşağıdaki genel ve diğer türlerle değiştirilmemiş çalışır:

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

Kısa ve tatlı, yeni dize oluşturmak için StringBuilder kullanır, son virgül kaldırmak için StringBuilder uzunluğunu birer birer küçültür ve CSV dizesini döndürür.

Bu Append()dizi + virgül eklemek için birden çok 's kullanmak için güncelledim . James'in geri bildirimlerinden bakmak için Reflector kullandım StringBuilder.AppendFormat(). Dışarı Dönüşler AppendFormat()kullanımlar bir StringBuilder sadece birden kullanmaktan daha bu bağlamda daha az verimli hale getirir biçim dizesi oluşturmak için Appends()'ın.


Gazoldum, teşekkürler Xavier .Net4'teki güncellemenin farkında değildim. Üzerinde çalıştığım proje henüz bir adım atmadı, bu yüzden şimdi yaya örneğimi kullanmaya devam edeceğim.
David Clarke

2
Bu sıfır öğe IEnumerable kaynak ile başarısız olur. sb.Length-- bir sınır kontrolüne ihtiyaç duyar.
James Dunne

Nice catch teşekkür James, bunu kullandığım bağlamda en az bir OrderId için "garanti". Sınır kontrolünü dahil etmek için hem örnek hem de kendi kodumu güncelledim (sadece emin olmak için).
David Clarke

@James Sanırım sb.Length diyorum - bir kesmek biraz sert. Etkili bir şekilde, her bir yinelemede yapmak yerine, sonuna kadar "if (yapılmadı)" testinden kaçınıyorum.
David Clarke

1
@James benim açımdan sık sık burada sorulan sorulara birden fazla doğru cevap var ve birine "hack" olarak atıfta bulunmam hangi yanlış anlaşmazlık anlamına gelir. Daniel'in yukarıdaki cevabını birleştirdiğim az sayıda rehber için muhtemelen mükemmel bir şekilde yeterli olacak ve kesinlikle cevabımdan daha özlü / okunabilir. Bu benim kod sadece bir yerde kullanıyorum ve ben sadece hiç bir sınırlayıcı olarak virgül kullanacak. YAGNI diyor ki, ihtiyacınız olmayacak bir şey inşa etmeyin. DRY, bir kereden fazla yapmam gerekirse uygulanabilir, bu noktada bir uzatma yöntemi oluşturacağım. HTH.
David Clarke

7

Biraz çirkin bir şey ama işe yarıyor:

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

Listeye dönüştürücüyü verdikten sonra size bir CSV verir (bu durumda d => d.DivisionID.ToString ("b")).

Hacky ama çalışıyor - belki bir uzatma yöntemi haline getirilebilir?


7

Diğer dillerde yaptığım yolu kullanarak bunu yaptım:

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

'İle çevremiz gerektiğinde özel ihtiyaç, örneğin:

        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

Bir yardımcı fonksiyonumuz var, böyle bir şey:

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

Kolayca birçok koleksiyona katılmak için kullanılabilir:

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

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

Unutmayın ki lambdadan önce toplama parametresi var çünkü intellisense daha sonra toplama türünü alıyor.

Zaten dize numaralandırmanız varsa, tek yapmanız gereken ToArray:

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

2
Neredeyse tamamen aynı şeyi yapan bir uzantı yöntemim var, çok yararlı: stackoverflow.com/questions/696850/…
LukeH 28:09

Evet, bunu kolayca .ToDelimitedString uzantısı yöntemi olarak yazabilirsiniz. Benim tek satır dize ile gitmek istiyorum. Yerine son char kırpma bir StringBuilder kullanmak yerine bir katılın.
Keith

3

ToArray kullanarak IList'i bir diziye dönüştürebilir ve sonra dizi üzerinde string.join komutunu çalıştırabilirsiniz.

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

3

.NET 3.5'te Linq uzantıları kullanılarak kolayca bir diziye dönüştürülebilirler.

   var stringArray = stringList.ToArray();

3

Başkaları tarafından listelenen yöntemlerden birini kullanarak bir diziye dönüştürdükten sonra aşağıdaki gibi bir şey de kullanabilirsiniz:

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

Düzenleme: İşte başka bir örnek


3

Bu makalede gerçekleşmeden önce bu sorunu çözdüm. Benim çözümüm aşağıdaki gibi gider:

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

Şöyle denir:

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

Ayrıca bu kadar kolay ifade edebilirdim ve daha da verimli olabilirdim:

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

3

Cevabım yukarıdaki Aggregate çözümüne benziyor ancak açık temsilci çağrıları olmadığından daha az çağrı yığını olmalı:

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

Tabii ki, imza sınırlayıcıdan bağımsız olacak şekilde genişletilebilir. Gerçekten sb.Remove () çağrısının hayranı değilim ve bir IEnumerable üzerinde düz bir while-loop olmak ve bir virgül yazma ya da değil belirlemek için MoveNext () kullanmak için refactor istiyorum. Etrafa dönersem bu çözümü söylerim.


Başlangıçta istediğim şudur:

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

Geçici dizi veya liste depolaması gerekmez ve hayır StringBuilder Remove()veyaLength-- kesmek kesmek gerekmez.

Benim çerçeve kütüphanesinde bu yöntem imzası birkaç varyasyonları dahil her kombinasyonunu yapılmış delimiterve converterkullanımı ile parametrelerini ","ve x.ToString()sırasıyla varsayılan olarak.


3

Umarım bu en basit yoldur

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

3

MySql yöntemi ile yapılır gibi dizeleri birleştirmek için iyi bir C # yöntemi ararken bu tartışma geldi CONCAT_WS(). Bu yöntem string.Join(), dizeler NULL veya boşsa ayırıcı işaretini eklememesi bakımından yöntemden farklıdır .

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

yalnızca Lastnamead boşsa geri döner

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

geri dönücek strLastname + ", "aynı durumda .

İlk davranışı isteyen aşağıdaki yöntemleri yazdım:

    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

Verimli bir şekilde yapmak için birkaç uzantı yöntemi yazdım:

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

Bu bağlıdır

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

        return s+delim+item;
    }

3
Dizeleri birleştirmek için + işlecini kullanmak harika değildir, çünkü her seferinde yeni bir dizenin ayrılmasına neden olur. Dahası, StringBuilder bir dizeye dolaylı olarak dökülebilmesine rağmen, sık sık (döngünüzün her yinelemesi) bir dize oluşturucuya sahip olma amacını büyük ölçüde bozar.
Daniel Fortunov

2

Sen kullanabilirsiniz .ToArray()üzerinde Listsve IEnumerablesve sonra kullanmak String.Join()istediğiniz gibi.


0

Katılmak istediğiniz dizeler Nesne Listesi'ndeyse, bunun gibi bir şey de yapabilirsiniz:

var studentNames = string.Join(", ", students.Select(x => x.name));
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.