将泛型列表转换为 CSV 字符串

我有一个整数值列表(List) ,希望生成一个逗号分隔的值字符串。即将列表中的所有项输出到一个以逗号分隔的列表中。

我的想法..。 将列表传递给一个方法。 2. 使用 stringBuilder 迭代列表并附加逗号 3. 测试最后一个字符,如果是逗号,删除它。

你有什么想法? 这是最好的办法吗?

如果我想在将来不仅处理整数(我当前的计划)而且处理字符串、长数、双精度数、布尔数等等,那么我的代码会发生什么变化呢?我想让它接受任何类型的列表。

200509 次浏览

框架已经为我们做了这么多,真是令人惊讶。

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() + "\"")。

如果你想了解更多关于这方面的有趣信息,请参阅 Eric Lippert 博客上的 逗号吹毛求疵

注意: 这是在.NET 4.0正式发布之前写的

IEnumerable<T> sequence;
string csv = String.Join(",", sequence);

使用重载 String.Join<T>(string, IEnumerable<T>)。此方法将自动投影每个元素 xx.ToString()

你可以使用 String.Join

String.Join(
",",
Array.ConvertAll(
list.ToArray(),
element => element.ToString()
)
);

您可以创建一个扩展方法,您可以调用任何 IEnumable:

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

在3.5,我仍然可以做到这一点。它更加简单,不需要 lambda。

String.Join(",", myList.ToArray<string>());

任何解决方案只有在列出(字符串的)列表时才有效

如果您有自己的对象的通用列表,比如 car 具有 n 个属性的 list (of car) ,则必须循环每个 car 对象的 PropertiesInfo。

看: http://www.csharptocsharp.com/generate-csv-from-generic-list

如果任何主体想要转换 自定义类对象而不是 字符串列表字符串列表的 list,那么用类的 csv 行表示重写类的 ToString 方法。

Public Class MyClass{
public int Id{get;set;}
public String PropertyA{get;set;}
public override string ToString()
{
return this.Id+ "," + this.PropertyA;
}
}

然后可以使用下面的代码将此类列表转换为具有 < em > 标题栏 的 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());

作为@Frank 从.NET 通用列表创建 CSV 文件给出的链接中的代码,有一个小问题,即每一行都以 ,结束,我修改了代码来去除它。希望能帮到别人。

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


}
}
}

我喜欢一个简单的扩展方法

 public static string ToCsv(this List<string> itemList)
{
return string.Join(",", itemList);
}

然后你可以调用原始列表中的方法:

string CsvString = myList.ToCsv();

比其他一些建议更干净,更容易阅读。

Http://cc.davelozinski.com/c-sharp/the-fastest-way-to-read-and-process-text-files

这个网站做了一些广泛的测试关于如何写一个文件使用缓冲写入器,逐行读取似乎是最好的方法,使用字符串生成器是最慢的方法之一。

我大量使用他的技术写东西归档工作得很好。

String 的问题。联接是指您没有处理值中已经存在逗号的情况。当逗号存在时,将引号中的值包围起来,并将所有现有的引号替换为双引号。

String.Join(",",{"this value has a , in it","This one doesn't", "This one , does"});

参见 CSV 模块

我将在这个 邮寄中对它进行深入的解释。我只是将代码粘贴在这里并做简短的描述。

下面是创建标题行的方法,它使用属性名作为列名。

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

CsvHelper 库在 Nuget 非常受欢迎,你值得拥有它,伙计! Https://github.com/joshclose/csvhelper/wiki/basics

使用 CsvHelper 非常简单,它的默认设置是为最常见的场景设置的。

这里有一些设置数据。

演员:

Id,FirstName,LastName
1,Arnold,Schwarzenegger
2,Matt,Damon
3,Christian,Bale

Cs (表示参与者的自定义类对象) :

public class Actor
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

使用 CsvReader 读取 CSV 文件:

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

一个通用的 ToCsv ()扩展方法:

  • 支持 Int16/32/64、 float、 double、 decal 和任何支持 ToString ()
  • 可选的自定义连接分隔符
  • 可选的自定义选择器
  • 可选的空/空处理规范(* 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);
}

不管出于什么原因,@AliUmair 将编辑恢复到他的答案,修复了他不能正常运行的代码,所以下面是没有文件访问错误并且正确处理 null 对象属性值的工作版本:

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


}
}
}

这是我的扩展方法,为了简单起见,它返回一个字符串,但是我的实现将文件写入数据湖。

它提供任何分隔符,向字符串添加引号(如果它们包含分隔符) ,并且交易将为空。

    /// <summary>
/// A class to hold extension methods for C# Lists
/// </summary>
public static class ListExtensions
{
/// <summary>
/// Convert a list of Type T to a CSV
/// </summary>
/// <typeparam name="T">The type of the object held in the list</typeparam>
/// <param name="items">The list of items to process</param>
/// <param name="delimiter">Specify the delimiter, default is ,</param>
/// <returns></returns>
public static string ToCsv<T>(this List<T> items, string delimiter = ",")
{
Type itemType = typeof(T);
var props = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);


var csv = new StringBuilder();


// Write Headers
csv.AppendLine(string.Join(delimiter, props.Select(p => p.Name)));


// Write Rows
foreach (var item in items)
{
// Write Fields
csv.AppendLine(string.Join(delimiter, props.Select(p => GetCsvFieldasedOnValue(p, item))));
}


return csv.ToString();
}


/// <summary>
/// Provide generic and specific handling of fields
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p"></param>
/// <param name="item"></param>
/// <returns></returns>
private static object GetCsvFieldasedOnValue<T>(PropertyInfo p, T item)
{
string value = "";


try
{
value = p.GetValue(item, null)?.ToString();
if (value == null) return "NULL";  // Deal with nulls
if (value.Trim().Length == 0) return ""; // Deal with spaces and blanks


// Guard strings with "s, they may contain the delimiter!
if (p.PropertyType == typeof(string))
{
value = string.Format("\"{0}\"", value);
}
}
catch (Exception ex)
{
throw ex;
}
return value;
}
}

用法:

 // Tab Delimited (TSV)
var csv = MyList.ToCsv<MyClass>("\t");

其他的答案也可以,但我的问题是从数据库加载未知数据,所以我需要一些比现有的更健壮的东西。

我想要符合以下要求的东西:

  • 能够在 excel 中打开
  • 必须能够以 Excel 兼容的方式处理日期时间格式
  • 必须自动排除链接实体(EF 导航属性)
  • 必须支持包含 "和分隔符 ,的列内容
  • 必须支持可为空的列
  • 必须支持大量的数据类型
    • 各种各样的数字
    • 伙计们
    • 约会时间
    • 自定义类型定义(即链接实体的名称)

出于兼容性原因,我使用 month/day/year格式进行日期导出

public static IReadOnlyDictionary<System.Type, Func<object, string>> CsvTypeFormats = new Dictionary<System.Type, Func<object, string>> {
// handles escaping column delimiter (',') and quote marks
{ typeof(string), x => string.IsNullOrWhiteSpace(x as string) ? null as string : $"\"{(x as string).Replace("\"", "\"\"")}\""},
{ typeof(DateTime), x => $"{x:M/d/yyyy H:m:s.fff}" },
{ typeof(DateTime?), x => x == null ? "" : $"{x:M/d/yyyy H:m:s.fff}" },
{ typeof(DateTimeOffset), x => $"{x:M/d/yyyy H:m:s.fff}" },
{ typeof(DateTimeOffset?), x => x == null ? "" : $"{x:M/d/yyyy H:m:s.fff}" },
};
public void WriteCsvContent<T>(ICollection<T> data, StringBuilder writer, IDictionary<System.Type, Func<object, string>> explicitMapping = null)
{
var typeMappings = CsvTypeFormats.ToDictionary(x=>x.Key, x=>x.Value);
if (explicitMapping != null) {
foreach(var mapping in explicitMapping) {
typeMappings[mapping.Key] = mapping.Value;
}
}
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => IsSimpleType(x.PropertyType))
.ToList();
// header row
writer.AppendJoin(',', props.Select(x => x.Name));
writer.AppendLine();
foreach (var item in data)
{
writer.AppendJoin(',',
props.Select(prop => typeMappings.ContainsKey(prop.PropertyType)
? typeMappings[prop.PropertyType](prop.GetValue(item))
: prop.GetValue(item)?.ToString() ?? ""
)
// escaping and special characters
.Select(x => x != null && x != "" ? $"\"{x.Replace("\"", "\"\"")}\"" : null)
);
writer.AppendLine();
}
}
private bool IsSimpleType(System.Type t)
{
return
t.IsPrimitive ||
t.IsValueType ||
t.IsEnum ||
(t == typeof(string)) ||
CsvTypeFormats.ContainsKey(t);
}

如果您的类使用字段而不是属性,那么将 GetProperties更改为 GetFields,将 PropertyType访问器更改为 FieldType