c# - Conversión de una lista genérica en una cadena CSV
list object to csv c# (13)
Tengo una lista de valores enteros (Lista) y me gustaría generar una cadena de valores delimitados por comas. Es decir, todos los elementos de la lista salen a una única lista delimitada por comas.
Mis pensamientos ... 1. pasar la lista a un método. 2. Utilice stringbuilder para iterar la lista y añada comas 3. Pruebe el último carácter y si es una coma, elimínelo.
¿Cuáles son tus pensamientos? Es esta la mejor manera?
¿Cómo cambiaría mi código si quisiera manejar no solo enteros (mi plan actual) sino cadenas, anhelos, dobles, bools, etc. en el futuro? Supongo que lo hacen aceptar una lista de cualquier tipo.
Como el código en el enlace proporcionado por @Frank Crea un archivo CSV desde una lista genérica de .NET había un pequeño problema de terminar cada línea con a ,
modifiqué el código para deshacerme de él. Espero que ayude a alguien.
/// <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);
}
}
}
Cualquier solución funciona solo si Lista una lista (de cadena)
Si tiene una lista genérica de su propia lista de Objetos como (auto) donde el auto tiene n propiedades, debe recorrer el Info de Propiedades de cada objeto de auto.
Mira: http://www.csharptocsharp.com/generate-csv-from-generic-list
El problema con String.Join es que no estás manejando el caso de una coma que ya existe en el valor. Cuando existe una coma, rodeas el valor en Citas y reemplazas todas las Citas existentes con comillas dobles.
String.Join(",",{"this value has a , in it","This one doesn''t", "This one , does"});
Ver módulo CSV
Es sorprendente lo que el Framework ya hace por nosotros.
List<int> myValues;
string csv = String.Join(",", myValues.Select(x => x.ToString()).ToArray());
Para el caso general:
IEnumerable<T> myList;
string csv = String.Join(",", myList.Select(x => x.ToString()).ToArray());
Como puede ver, efectivamente no es diferente. Tenga en cuenta que es posible que necesite ajustar x.ToString()
entre comillas (es decir, "/"" + x.ToString() + "/""
) en caso de que x.ToString()
contenga comas.
Para una lectura interesante sobre una pequeña variante de esto: vea Coma Qubbling en el blog de Eric Lippert.
Nota: Esto fue escrito antes .NET 4.0 fue lanzado oficialmente. Ahora solo podemos decir
IEnumerable<T> sequence;
string csv = String.Join(",", sequence);
utilizando la sobrecarga String.Join<T>(string, IEnumerable<T>)
. Este método proyectará automáticamente cada elemento x
a x.ToString()
.
La biblioteca CsvHelper es muy popular en Nuget. ¡Tú lo vales, hombre! https://github.com/JoshClose/CsvHelper/wiki/Basics
Usar CsvHelper es realmente fácil. Su configuración predeterminada está configurada para los escenarios más comunes.
Aquí hay un poco de datos de configuración.
Actors.csv:
Id,FirstName,LastName
1,Arnold,Schwarzenegger
2,Matt,Damon
3,Christian,Bale
Actor.cs (objeto de clase personalizado que representa un actor):
public class Actor
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Leyendo el archivo CSV usando CsvReader:
var csv = new CsvReader( new StreamReader( "Actors.csv" ) );
var actorsList = csv.GetRecords ();
Escribir en un archivo CSV.
using (var csv = new CsvWriter( new StreamWriter( "Actors.csv" ) ))
{
csv.WriteRecords( actorsList );
}
Lo explico en profundidad en esta post . Simplemente pegaré el código aquí con breves descripciones.
Aquí está el método que crea la fila del encabezado. Utiliza los nombres de propiedad como nombres de columna.
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);
}
Este método crea todas las filas de valores
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);
}
}
Y aquí está el método que los reúne y crea el archivo real.
public static void CreateCSV<T>(List<T> list, string filePath)
{
using (StreamWriter sw = new StreamWriter(filePath))
{
CreateHeader(list, sw);
CreateRows(list, sw);
}
}
Me gusta un buen método de extensión simple
public static string ToCsv(this List<string> itemList)
{
return string.Join(",", itemList);
}
Entonces puede simplemente llamar al método en la lista original:
string CsvString = myList.ToCsv();
Más limpio y fácil de leer que algunas de las otras sugerencias.
Puede crear un método de extensión al que pueda llamar en cualquier 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());
}
Entonces puede simplemente llamar al método en la lista original:
string commaSeparated = myList.JoinStrings(", ");
Puedes usar String.Join
.
String.Join(
",",
Array.ConvertAll(
list.ToArray(),
element => element.ToString()
)
);
Si un cuerpo desea convertir una lista de objetos de clase personalizados en lugar de una lista de cadenas , anule el método ToString de su clase con una representación de fila csv de su clase.
Public Class MyClass{
public int Id{get;set;}
public String PropertyA{get;set;}
public override string ToString()
{
return this.Id+ "," + this.PropertyA;
}
}
A continuación, se puede usar el siguiente código para convertir esta lista de clase en CSV con columna de encabezado
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());
Un método de extensión ToCsv () de propósito general:
- Admite Int16 / 32/64, flotante, doble, decimal y cualquier cosa que soporte ToString ()
- Separador de unión personalizada opcional
- Selector personalizado opcional
- Especificación de manejo nulo / vacío opcional (* sobrecargas de Opt ())
Ejemplos de uso:
"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
Implementación
/// <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);
}
en 3.5, todavía pude hacer esto. Es mucho más simple y no necesita lambda.
String.Join(",", myList.ToArray<string>());
http://cc.davelozinski.com/c-sharp/the-fastest-way-to-read-and-process-text-files
Este sitio web realizó algunas pruebas exhaustivas sobre cómo escribir en un archivo utilizando escritor en búfer, la lectura línea por línea parece ser la mejor, ya que el generador de cadenas fue uno de los más lentos.
Utilizo sus técnicas mucho para escribir cosas para archivar, funciona bien.