Создание списка через запятую из IList <string> или IEnumerable <string>

851

Какой самый чистый способ создать список строковых значений через 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, стоит помнить, что просто вставка запятых между элементами недостаточна и приведет к сбою в случае кавычек и запятых в исходных данных.
транжира

Ответы:

1448

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

Гораздо проще :)

Джон Скит
источник
Вспомогательный метод включает в себя создание двух ненужных списков. Это действительно лучший способ решить проблему? Почему бы не сделать это самостоятельно в цикле foreach?
Эрик
4
Вспомогательный метод создает только один список и один массив. Дело в том, что результатом должен быть массив, а не список ... и вам нужно знать размер массива перед началом. В соответствии с передовой практикой вам не следует перечислять источник в LINQ более одного раза, если только вам не нужно - это может быть связано с различными неприятными вещами. Таким образом, вы остаетесь с чтением в буферы и изменением размера по ходу работы - и это именно то, что List<T>нужно. Зачем изобретать велосипед?
Джон Скит
9
Нет, дело в том, что результатом должна быть объединенная строка. Нет необходимости создавать новый список или новый массив для достижения этой цели. Такой менталитет .NET меня огорчает.
Эрик
38
Вот и все. Каждый ответ ведет к Джону Скиту. Я просто собираюсь измениться. BuyBooks = 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 и получают список
номеров
1
Сначала нужно выбрать строку, хотя вы можете создать метод расширения, который это делает. str = emps.Select(e => e.SSN).Join(",")
Ксавье Пойнас
65

Самый простой способ, который я вижу, это использовать Aggregateметод LINQ :

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
Даниэль Фортунов
источник
20
Это не только сложнее (IMO), чем ToArray + Join, но и несколько неэффективно - с большой входной последовательностью это начнет очень плохо работать.
Джон Скит
35
Тем не менее, это самая красивая.
Мерритт
2
Вы можете кормить Aggregate семя StringBuilder, тогда ваш Aggregate Func становится Func<StringBuilder,string,StringBuider>. Затем просто вызовите ToString()возвращенный StringBuilder. Это, конечно, не так красиво, хотя :)
Мэтт Грир
3
Это самый ясный способ сделать то, что задал вопрос ИМХО.
Дерек Моррисон
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 (как Ксавье указал в своем ответе).
Дерек Моррисон
С точки зрения новичка в .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

Я немного опоздал на эту дискуссию, но это мой вклад. Я должен 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(), чтобы добавить строку + запятую. Из отзывов Джеймса я использовал Reflector, чтобы посмотреть StringBuilder.AppendFormat(). Оказывается, AppendFormat()использует StringBuilder для создания строки формата, что делает ее менее эффективной в этом контексте, чем просто использование нескольких Appends().

Дэвид Кларк
источник
Gazumped, спасибо Ксавье, я не знал об этом обновлении в .Net4. Проект, над которым я работаю, еще не сделал скачок, поэтому я буду продолжать использовать мой пример с пешеходами.
Дэвид Кларк
2
Это не удастся с IEnumerable с нулевым элементом. sb.Length-- нужна проверка границ.
Джеймс Данн
Хороший улов благодаря Джеймсу, в контексте, где я использую это, я "гарантированно" имею по крайней мере один OrderId. Я обновил и пример, и собственный код, чтобы включить проверку границ (просто чтобы быть уверенным).
Дэвид Кларк
@ Джеймс, я думаю, что звоню sb.Length-- хак немного резок. По сути, я просто избегаю вашего теста «if (notdone)» до конца, а не делаю его на каждой итерации.
Дэвид Кларк
1
@ Джеймс, с моей точки зрения, часто есть более одного правильного ответа на вопросы, заданные здесь, и ссылка на него как на «взлом» подразумевает, что это неправильно, что я бы оспаривал. Для небольшого числа руководств, которые я объединяю, ответ Даниэля, приведенный выше, вероятно, будет вполне адекватным, и он, безусловно, будет более кратким / читабельным, чем мой ответ. Я использую это только в одном месте в моем коде, и я буду использовать запятую только в качестве разделителя. ЯГНИ говорит, не создавай то, что тебе не нужно. DRY применимо, если мне нужно было сделать это более одного раза, после чего я бы создал метод расширения. НТН.
Дэвид Кларк
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()));
Serhio
источник
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)); 
оборота SpaceKat
источник
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 (), и я хотел бы изменить его на прямой цикл while над 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()метода тем, что он не добавляет знак разделителя, если строки пустые или пустые.

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;
    }
Håkon Seljåsen
источник
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()как вы хотели.

JoshJordan
источник
0

Если строки, к которым вы хотите присоединиться, находятся в списке объектов, то вы также можете сделать что-то вроде этого:

var studentNames = string.Join(", ", students.Select(x => x.name));
Сакшам Чаудхари
источник