Каков самый чистый способ создания списка строковых значений, разделенных запятыми, из IList<string>
или IEnumerable<string>
?
String.Join(...)
работает с string[]
, поэтому может быть громоздко работать, когда типы, такие как IList<string>
или IEnumerable<string>
, не могут быть легко преобразованы в строковый массив.
.NET 4 +
IList<string> strings = new List<string>{"1","2","testing"};
string joined = string.Join(",", strings);
Подробные и предварительные версии 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);
Гораздо проще:)
List<T>
. Зачем изобретать велосипед?
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)
Самый простой способ, которым я могу это сделать, - использовать метод LINQ Aggregate
:
string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
Я думаю, что самый чистый способ создания списка строковых значений, разделенных запятыми, просто:
string.Join<string>(",", stringEnumerable);
Вот полный пример:
IEnumerable<string> stringEnumerable= new List<string>();
stringList.Add("Comma");
stringList.Add("Separated");
string.Join<string>(",", stringEnumerable);
Нет необходимости создавать вспомогательную функцию, она встроена в .NET 4.0 и выше.
Так как я достиг этого при поиске для присоединения к определенному свойству списка объектов (а не к ToString()), это дополнение к принятому ответу:
var commaDelimited = string.Join(",", students.Where(i => i.Category == studentCategory)
.Select(i => i.FirstName));
Здесь другой метод расширения:
public static string Join(this IEnumerable<string> source, string separator)
{
return string.Join(separator, source);
}
Придя немного поздно к этому обсуждению, но это мой вклад. У меня есть 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();});
Short and sweet, использует StringBuilder для построения новой строки, сокращает длину StringBuilder на единицу для удаления последней запятой и возвращает CSV-строку.
Я обновил это, чтобы использовать несколько Append()
для добавления строки + запятой. От обратной связи Джеймса я использовал Reflector, чтобы посмотреть на StringBuilder.AppendFormat()
. Выключается AppendFormat()
использует StringBuilder для построения строки формата, что делает ее менее эффективной в этом контексте, чем просто использование нескольких Appends()
.
Сравнивая по производительности, победитель получает "Loop it, Join it and do back step". Фактически "перечислимое и ручное перемещение вперед" одинаково хорошо (см. 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 был использован
Вот так, как я это сделал, используя то, как я это сделал на других языках:
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();
}
Что-то немного уродливое, но оно работает:
string divisionsCSV = String.Join(",", ((List<IDivisionView>)divisions).ConvertAll<string>(d => d.DivisionID.ToString("b")).ToArray());
Дает вам CSV из списка после того, как вы дадите ему конвертер (в этом случае d = > d.DivisionID.ToString( "b" )).
Хакки, но работает - может быть сделано в метод расширения, возможно?
Конкретная необходимость, когда мы должны окружать ', например:
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()));
Я только что решил эту проблему, прежде чем переходить к этой статье. Мое решение выглядит примерно так:
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));
У нас есть функция полезности, что-то вроде этого:
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() );
Я пришел к этому обсуждению, ища хороший метод С# для объединения строк, как это делается с помощью метода MySql CONCAT_WS()
. Этот метод отличается от метода string.Join()
тем, что он не добавляет знак разделителя, если строки NULL или empty.
CONCAT_WS (',', tbl.Lastname, tbl.Firstname)
будет возвращать только Lastname
, если firstname пусто, а
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;
}
Надеюсь, это самый простой способ
string Commaseplist;
string[] itemList = { "Test1", "Test2", "Test3" };
Commaseplist = string.join(",",itemList);
Console.WriteLine(Commaseplist); //Outputs Test1,Test2,Test3
Мой ответ подобен приведенному выше решению 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(), и я бы хотел реорганизовать его как прямолинейный цикл 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()
как значения по умолчанию соответственно.
Вы также можете использовать что-то вроде следующего после того, как вы его преобразуете в массив, используя один из методов, перечисленных другими:
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();
}
}
}
Изменить: Вот еще один пример
Их можно легко преобразовать в массив с использованием расширений Linq в .NET 3.5.
var stringArray = stringList.ToArray();
вы можете преобразовать IList в массив с помощью ToArray, а затем запустить команду string.join в массиве.
Dim strs As New List(Of String)
Dim arr As Array
arr = strs.ToArray
Я написал несколько методов расширения, чтобы сделать это так эффективно:
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;
}
Вы можете использовать .ToArray()
в Lists
и IEnumerables
, а затем использовать String.Join()
по своему усмотрению.
public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)