Створення списку, розділеного комами, від IList <string> або IEnumerable <string>


848

Який найчистіший спосіб створити розділений комою список значень рядків з IList<string>або IEnumerable<string>?

String.Join(...)працює на string[]так, що може бути громіздким для роботи, коли такі типи, як, IList<string>або IEnumerable<string>не можуть бути легко перетворені в рядковий масив.


5
Ой ... ой. Я пропустив додавання методу розширення ToArray в 3.5:public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
Даніель Фортунов,

1
Якщо ви підійшли до цього питання, шукаючи засоби для написання CSV, варто пам’ятати, що просто вставляти коми між елементами недостатньо і спричинить збій у разі лапок і коми у вихідних даних.
марнотрат

Відповіді:


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

Останнє хоч трохи переповнює :)

Це, мабуть, найпростіший спосіб зробити це, причому досить успішним - є й інші питання про те, як саме виглядає вистава, включаючи (але не обмежуючись цим) цей .

Станом на .NET 4.0 доступно більше перевантажень string.Join, тому ви можете просто написати:

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

Набагато простіше :)


Хелперний метод включає створення двох непотрібних списків. Це справді найкращий спосіб вирішити проблему? Чому б не об'єднати його в циклі передбачення?
Ерік

4
Хелперний метод створює лише один список і один масив. Справа в тому, що результатом повинен бути масив, а не список ... і вам потрібно знати розмір масиву, перш ніж починати. Найкраща практика говорить, що ви не повинні перераховувати джерело більше ніж один раз у LINQ, якщо цього не потрібно - це може робити всілякі противні речі. Отже, вам залишається читати в буфери та змінювати розмір у процесі руху - саме це і List<T>робить. Навіщо винаходити колесо?
Джон Скіт

9
Ні, справа в тому, що для результату потрібно бути з'єднаний рядок. Для досягнення цієї мети не потрібно створювати новий список чи новий масив. Такий менталітет .NET мене сумує.
Ерік

38
Це воно. Кожна відповідь веде до Джона Скіта. Я просто збираюся варіювати PurchaseBooks = AmazonContainer.Where (p => p.Author == "Джон Скіт"). Виберіть ();
Захарі Скотт

3
@ codeMonkey0110: Ну, немає сенсу в тому, щоб мати вираз запиту або викликати ToList. Це чудово використовувати, string myStr = string.Join(",", foo.Select(a => a.someInt.ToString()))хоча.
Джон Скіт

179

FYI, версія .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 ()?
Філіп Лавуа

Якраз збирався прокоментувати це на відповідь Йона. Дякуємо за згадування.
Dan Bechard

2
У будь-якому випадку робити це на властивості об'єкта? (Приклад: IEnumerable <Employee> і об'єкт Employee має властивість рядок .SSN на ньому, і отримати список розділених комами ПЛА - х.)
granadaCoder

1
Спершу потрібно вибрати рядок, хоча ви можете створити метод розширення, який це робить. str = emps.Select(e => e.SSN).Join(",")
Xavier Poinas

65

Найпростіший спосіб, як я це бачу, - це використовувати Aggregateметод LINQ :

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

20
Це не тільки складніше (IMO), ніж ToArray + Join, це також дещо неефективно - з великою послідовністю введення, це почне працювати дуже погано.
Джон Скіт

35
Все-таки це найкрасивіше.
Меррітт

2
Ви можете годувати Агрегатом насіння StringBuilder, тоді ваш агрегатний функція стане Func<StringBuilder,string,StringBuider>. Тоді просто зателефонуйте ToString()на повернутий StringBuilder. Звичайно, це не так гарно :)
Метт Грін

3
Це найясніший спосіб зробити те, про що поставило питання ІМХО.
Дерек Моррісон


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

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

Прийшов трохи пізно до цієї дискусії, але це мій внесок fwiw. У мене є 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 на одну, щоб видалити останню кому і повертає рядок CSV.

Я оновив це для використання множини Append()'s для додавання рядка + кома З відгуків Джеймса я використовував Reflector, щоб подивитися StringBuilder.AppendFormat(). Виявляється, AppendFormat()використовується StringBuilder для побудови рядка формату, що робить його менш ефективним у цьому контексті, ніж просто використання множини Appends().


Зітхнувши, дякую Ксав'є, я не знав про це оновлення в .Net4. Проект, над яким я працюю, поки не зробив цього стрибка, тому тим часом я продовжую використовувати свій пішохідний приклад.
Девід Кларк

2
Це не вдасться з нульовим джерелом IEnumerable. sb.Length-- потрібна перевірка меж.
Джеймс Данне

Хороший улов дякую Джеймсу, в контексті, коли я використовую це, я "гарантовано" маю принаймні один OrderId. Я оновив і приклад, і власний код, щоб включити перевірку меж (просто напевне).
Девід Кларк

@James Я думаю, що дзвонити на sb.Length-- хак трохи суворий. Ефективно я просто уникаю вашого тесту "if (notdone)" до кінця, а не робити це в кожній ітерації.
Девід Кларк

1
@James, я можу сказати, що тут часто є більше, ніж одна правильна відповідь на задані тут питання, і згадуючи, що "хак" означає, що це неправильно, що я б заперечував. Зважаючи на невелику кількість посібників, які я сполучаю, відповідь Даніеля вище, ймовірно, буде цілком адекватною, і це, звичайно, більш лаконічний / читабельний, ніж моя відповідь. Я використовую це лише в одному місці свого коду, і я буду використовувати колись кому лише як роздільник. YAGNI каже, що не будуйте те, що вам не потрібно. 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() );

Зауважте, що у нас є парам колекції перед лямбда, тому що intellisense потім підбирає тип колекції.

Якщо у вас вже є перерахування рядків, все, що вам потрібно зробити, це ToArray:

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

2
У мене є метод розширення , який робить майже те ж саме, дуже корисно: stackoverflow.com/questions/696850 / ...
LukeH

Так, ви можете написати це як метод розширення .ToDelimitedString досить легко. Я б пішов з моєю однорядковою рядком. З'єднайте одну, а не використовуючи StringBuilder для обрізки останнього знака.
Кіт

3

ви можете перетворити IList в масив за допомогою ToArray, а потім виконати команду string.join на масиві.

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

3

Їх можна легко перетворити на масив, використовуючи розширення Linq в .NET 3.5.

   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

Моя відповідь нагадує вище рішення Aggregate, але воно повинно бути менш важким стеком викликів, оскільки немає явних викликів делегата:

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.Imename, 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 можна неявно передати на рядок, але це часто (кожна ітерація вашого циклу) значною мірою переможе мету створення конструктора рядків.
Даніель Фортунов

2

Ви можете використовувати .ToArray()на Listsі IEnumerables, а потім використовувати , String.Join()як ви хотіли.


0

Якщо рядки, до яких ви хочете приєднатись, знаходяться у списку об’єктів, тоді ви також можете зробити щось подібне:

var studentNames = string.Join(", ", students.Select(x => x.name));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.