Преобразование общего списка в строку CSV
У меня есть список целых значений (List) и хотел бы сгенерировать строку значений с разделителями-запятыми. Это все элементы в списке выводят в один список с разделителями-запятыми.
Мои мысли...
1. Передайте список методу.
2. Используйте stringbuilder для перебора списка и добавления запятых
3. Протестируйте последний символ и, если это запятая, удалите его.
Каковы ваши мысли? Это лучший способ?
Как изменился бы мой код, если бы я хотел обрабатывать не только целые числа (мой текущий план), но строки, длинные, двойные, bools и т.д. и т.д. в будущем? Я предполагаю, что он принимает список любого типа.
Ответы
Ответ 1
Удивительно, что Framework уже делает для нас.
List<int> myValues;
string csv = String.Join(",", myValues.Select(x => x.ToString()).ToArray());
В общем случае:
IEnumerable<T> myList;
string csv = String.Join(",", myList.Select(x => x.ToString()).ToArray());
Как вы можете видеть, это практически не отличается. Опасайтесь, что вам может понадобиться на самом деле обернуть x.ToString()
в кавычки (т.е. "\"" + x.ToString() + "\""
), если x.ToString()
содержит запятые.
Для интересного прочитайте небольшой вариант этого: см. Comma Quibbling в блоге Эрика Липперта.
Примечание. Это было написано до того, как был выпущен .NET 4.0. Теперь мы можем просто сказать
IEnumerable<T> sequence;
string csv = String.Join(",", sequence);
используя перегрузку String.Join<T>(string, IEnumerable<T>)
. Этот метод автоматически проецирует каждый элемент x
на x.ToString()
.
Ответ 2
в 3.5, я все еще мог это сделать. Это намного проще и не нуждается в лямбда.
String.Join(",", myList.ToArray<string>());
Ответ 3
Вы можете создать метод расширения, который вы можете вызвать в любом IEnumerable:
public static string JoinStrings<T>(
this IEnumerable<T> values, string separator)
{
var stringValues = values.Select(item =>
(item == null ? string.Empty : item.ToString()));
return string.Join(separator, stringValues.ToArray());
}
Затем вы можете просто вызвать метод в исходном списке:
string commaSeparated = myList.JoinStrings(", ");
Ответ 4
Вы можете использовать String.Join
.
String.Join(
",",
Array.ConvertAll(
list.ToArray(),
element => element.ToString()
)
);
Ответ 5
Если какой-либо орган хочет преобразовать список объектов пользовательского класса вместо списка строки, то переопределите метод ToString вашего класса с представлением строки csv вашего класса.
Public Class MyClass{
public int Id{get;set;}
public String PropertyA{get;set;}
public override string ToString()
{
return this.Id+ "," + this.PropertyA;
}
}
Затем следующий код может быть использован для преобразования этого списка классов в CSV с колонкой заголовка
string csvHeaderRow = String.Join(",", typeof(MyClass).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(x => x.Name).ToArray<string>()) + Environment.NewLine;
string csv= csvHeaderRow + String.Join(Environment.NewLine, MyClass.Select(x => x.ToString()).ToArray());
Ответ 6
Поскольку код в ссылке, предоставленной @Frank Создать файл CSV из общего списка .NET, возникла небольшая проблема с завершением каждой строки с помощью ,
. Я изменил код, чтобы избавиться от него. Надеюсь, это кому-нибудь поможет.
/// <summary>
/// Creates the CSV from a generic list.
/// </summary>;
/// <typeparam name="T"></typeparam>;
/// <param name="list">The list.</param>;
/// <param name="csvNameWithExt">Name of CSV (w/ path) w/ file ext.</param>;
public static void CreateCSVFromGenericList<T>(List<T> list, string csvCompletePath)
{
if (list == null || list.Count == 0) return;
//get type from 0th member
Type t = list[0].GetType();
string newLine = Environment.NewLine;
if (!Directory.Exists(Path.GetDirectoryName(csvCompletePath))) Directory.CreateDirectory(Path.GetDirectoryName(csvCompletePath));
if (!File.Exists(csvCompletePath)) File.Create(csvCompletePath);
using (var sw = new StreamWriter(csvCompletePath))
{
//make a new instance of the class name we figured out to get its props
object o = Activator.CreateInstance(t);
//gets all properties
PropertyInfo[] props = o.GetType().GetProperties();
//foreach of the properties in class above, write out properties
//this is the header row
sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);
//this acts as datarow
foreach (T item in list)
{
//this acts as datacolumn
var row = string.Join(",", props.Select(d => item.GetType()
.GetProperty(d.Name)
.GetValue(item, null)
.ToString())
.ToArray());
sw.Write(row + newLine);
}
}
}
Ответ 7
Любое решение работает только в том случае, если List list (string)
Если у вас есть общий список ваших собственных объектов, таких как список (автомобиль), где у автомобиля есть n свойств, вы должны зациклировать PropertyInfo каждого объекта автомобиля.
Посмотрите: http://www.csharptocsharp.com/generate-csv-from-generic-list
Ответ 8
Мне нравится хороший простой метод расширения
public static string ToCsv(this List<string> itemList)
{
return string.Join(",", itemList);
}
Затем вы можете просто вызвать метод в исходном списке:
string CsvString = myList.ToCsv();
Чище и легче читать, чем некоторые другие предложения.
Ответ 9
Я подробно объясню это в post. Я просто вставляю код здесь с краткими описаниями.
Здесь метод, который создает строку заголовка. Он использует имена свойств как имена столбцов.
private static void CreateHeader<T>(List<T> list, StreamWriter sw)
{
PropertyInfo[] properties = typeof(T).GetProperties();
for (int i = 0; i < properties.Length - 1; i++)
{
sw.Write(properties[i].Name + ",");
}
var lastProp = properties[properties.Length - 1].Name;
sw.Write(lastProp + sw.NewLine);
}
Этот метод создает все строки значений
private static void CreateRows<T>(List<T> list, StreamWriter sw)
{
foreach (var item in list)
{
PropertyInfo[] properties = typeof(T).GetProperties();
for (int i = 0; i < properties.Length - 1; i++)
{
var prop = properties[i];
sw.Write(prop.GetValue(item) + ",");
}
var lastProp = properties[properties.Length - 1];
sw.Write(lastProp.GetValue(item) + sw.NewLine);
}
}
И вот метод, который объединяет их и создает фактический файл.
public static void CreateCSV<T>(List<T> list, string filePath)
{
using (StreamWriter sw = new StreamWriter(filePath))
{
CreateHeader(list, sw);
CreateRows(list, sw);
}
}
Ответ 10
Библиотека CsvHelper очень популярна в Nuget. Вы стоите того, человек!
https://github.com/JoshClose/CsvHelper/wiki/Basics
Использование CsvHelper очень просто. Его настройки по умолчанию настраиваются для наиболее распространенных сценариев.
Ниже приведены небольшие данные настройки.
Actors.csv:
Id,FirstName,LastName
1,Arnold,Schwarzenegger
2,Matt,Damon
3,Christian,Bale
Actor.cs(пользовательский объект класса, представляющий актера):
public class Actor
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Чтение CSV файла с помощью CsvReader:
var csv = new CsvReader( new StreamReader( "Actors.csv" ) );
var actorsList = csv.GetRecords();
Запись в файл CSV.
using (var csv = new CsvWriter( new StreamWriter( "Actors.csv" ) ))
{
csv.WriteRecords( actorsList );
}
Ответ 11
http://cc.davelozinski.com/c-sharp/the-fastest-way-to-read-and-process-text-files
На этом веб-сайте было проведено обширное тестирование о том, как писать в файл с использованием буферизованного писателя, чтение строки за строкой, по-видимому, является лучшим способом, использование построителя строк было одним из самых медленных.
Я использую его методы для написания материала, чтобы он работал хорошо.
Ответ 12
Проблема с String.Join заключается в том, что вы не обрабатываете случай уже существующей в значении запятой. Когда существует запятая, вы окружаете значение в котировках и заменяете все существующие котировки двойными кавычками.
String.Join(",",{"this value has a , in it","This one doesn't", "This one , does"});
См. CSV Module
Ответ 13
Метод расширения ToCsv() общего назначения:
- Поддерживает Int16/32/64, float, double, decimal и любую поддержку
ToString()
- Дополнительный настраиваемый разделитель соединений
- Дополнительный настраиваемый селектор
- Дополнительная спецификация null/empty обработки (перегрузки * Opt())
Примеры использования:
"123".ToCsv() // "1,2,3"
"123".ToCsv(", ") // "1, 2, 3"
new List<int> { 1, 2, 3 }.ToCsv() // "1,2,3"
new List<Tuple<int, string>>
{
Tuple.Create(1, "One"),
Tuple.Create(2, "Two")
}
.ToCsv(t => t.Item2); // "One,Two"
((string)null).ToCsv() // throws exception
((string)null).ToCsvOpt() // ""
((string)null).ToCsvOpt(ReturnNullCsv.WhenNull) // null
Реализация
/// <summary>
/// Specifies when ToCsv() should return null. Refer to ToCsv() for IEnumerable[T]
/// </summary>
public enum ReturnNullCsv
{
/// <summary>
/// Return String.Empty when the input list is null or empty.
/// </summary>
Never,
/// <summary>
/// Return null only if input list is null. Return String.Empty if list is empty.
/// </summary>
WhenNull,
/// <summary>
/// Return null when the input list is null or empty
/// </summary>
WhenNullOrEmpty,
/// <summary>
/// Throw if the argument is null
/// </summary>
ThrowIfNull
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsv<T>(
this IEnumerable<T> values,
string joinSeparator = ",")
{
return ToCsvOpt<T>(values, null /*selector*/, ReturnNullCsv.ThrowIfNull, joinSeparator);
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="selector">An optional selector</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsv<T>(
this IEnumerable<T> values,
Func<T, string> selector,
string joinSeparator = ",")
{
return ToCsvOpt<T>(values, selector, ReturnNullCsv.ThrowIfNull, joinSeparator);
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="returnNullCsv">Return mode (refer to enum ReturnNullCsv).</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsvOpt<T>(
this IEnumerable<T> values,
ReturnNullCsv returnNullCsv = ReturnNullCsv.Never,
string joinSeparator = ",")
{
return ToCsvOpt<T>(values, null /*selector*/, returnNullCsv, joinSeparator);
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="selector">An optional selector</param>
/// <param name="returnNullCsv">Return mode (refer to enum ReturnNullCsv).</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsvOpt<T>(
this IEnumerable<T> values,
Func<T, string> selector,
ReturnNullCsv returnNullCsv = ReturnNullCsv.Never,
string joinSeparator = ",")
{
switch (returnNullCsv)
{
case ReturnNullCsv.Never:
if (!values.AnyOpt())
return string.Empty;
break;
case ReturnNullCsv.WhenNull:
if (values == null)
return null;
break;
case ReturnNullCsv.WhenNullOrEmpty:
if (!values.AnyOpt())
return null;
break;
case ReturnNullCsv.ThrowIfNull:
if (values == null)
throw new ArgumentOutOfRangeException("ToCsvOpt was passed a null value with ReturnNullCsv = ThrowIfNull.");
break;
default:
throw new ArgumentOutOfRangeException("returnNullCsv", returnNullCsv, "Out of range.");
}
if (selector == null)
{
if (typeof(T) == typeof(Int16) ||
typeof(T) == typeof(Int32) ||
typeof(T) == typeof(Int64))
{
selector = (v) => Convert.ToInt64(v).ToStringInvariant();
}
else if (typeof(T) == typeof(decimal))
{
selector = (v) => Convert.ToDecimal(v).ToStringInvariant();
}
else if (typeof(T) == typeof(float) ||
typeof(T) == typeof(double))
{
selector = (v) => Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture);
}
else
{
selector = (v) => v.ToString();
}
}
return String.Join(joinSeparator, values.Select(v => selector(v)));
}
public static string ToStringInvariantOpt(this Decimal? d)
{
return d.HasValue ? d.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Decimal d)
{
return d.ToString(CultureInfo.InvariantCulture);
}
public static string ToStringInvariantOpt(this Int64? l)
{
return l.HasValue ? l.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Int64 l)
{
return l.ToString(CultureInfo.InvariantCulture);
}
public static string ToStringInvariantOpt(this Int32? i)
{
return i.HasValue ? i.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Int32 i)
{
return i.ToString(CultureInfo.InvariantCulture);
}
public static string ToStringInvariantOpt(this Int16? i)
{
return i.HasValue ? i.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Int16 i)
{
return i.ToString(CultureInfo.InvariantCulture);
}
Ответ 14
По какой-то причине @AliUmair отменил редактирование своего ответа, исправляющего его код, который не работает как есть, поэтому вот рабочая версия, которая не имеет ошибки доступа к файлу и правильно обрабатывает нулевые значения свойств объекта:
/// <summary>
/// Creates the CSV from a generic list.
/// </summary>;
/// <typeparam name="T"></typeparam>;
/// <param name="list">The list.</param>;
/// <param name="csvNameWithExt">Name of CSV (w/ path) w/ file ext.</param>;
public static void CreateCSVFromGenericList<T>(List<T> list, string csvCompletePath)
{
if (list == null || list.Count == 0) return;
//get type from 0th member
Type t = list[0].GetType();
string newLine = Environment.NewLine;
if (!Directory.Exists(Path.GetDirectoryName(csvCompletePath))) Directory.CreateDirectory(Path.GetDirectoryName(csvCompletePath));
using (var sw = new StreamWriter(csvCompletePath))
{
//make a new instance of the class name we figured out to get its props
object o = Activator.CreateInstance(t);
//gets all properties
PropertyInfo[] props = o.GetType().GetProperties();
//foreach of the properties in class above, write out properties
//this is the header row
sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);
//this acts as datarow
foreach (T item in list)
{
//this acts as datacolumn
var row = string.Join(",", props.Select(d => $"\"{item.GetType().GetProperty(d.Name).GetValue(item, null)?.ToString()}\"")
.ToArray());
sw.Write(row + newLine);
}
}
}