将通用列表转换为CSV字符串


139

我有一个整数值列表(列表),并且想生成一个用逗号分隔的字符串。那就是列表中的所有项目输出到一个逗号分隔的列表中。

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

你觉得呢?你有没有什么想法?这是最好的方法吗?

如果将来我不仅要处理整数(当前的计划)而且要处理字符串,长整型,双精度型,布尔型等,我的代码将如何更改?我想让它接受任何类型的列表。

Answers:


243

框架已经为我们做的事情真令人惊奇。

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博客上的Comma Quibbling

注意:这是在.NET 4.0正式发布之前编写的。现在我们可以说

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

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


List<int>Select除非缺少某些内容,否则框架3.5 中没有方法。
2014年

2
@ajeh:您可能缺少using声明。
杰森2014年

具体进口哪个?
ajeh 2014年

1
尝试System.Linq.Enumerable(当然,您需要System.Core.dll组装,但是大概已经有了)。你看,List<int> 从未Select一个方法。而是System.Linq.Enumerable将定义Select为的扩展方法IEnumerable<T>,是其中List<int>的一个示例。因此,您需要System.Linq.Enumerable在导入中选择此扩展方法。
杰森

如果您要处理数字值和逗号(根据语言环境而定)是个问题,则可以选择一种x.ToString(CultureInfo.InvariantCulture)。这将使用句点作为小数点分隔符。
heltonbiker

15

在3.5中,我仍然能够做到这一点。它更简单,不需要lambda。

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

ToArray()的方法List<int>不能在框架3.5中与类型实参一起使用,除非我遗漏了一些东西。
2014年

辉煌。由于使用了子ToString(),因此不需要ToArray <string>。
基督教徒

11

您可以创建可在任何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(", ");

7

您可以使用String.Join

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

无需在ConvertAll此处调用时指定泛型类型参数-两者intstring都会被推断出来。
帕维尔米纳夫

1
而不是执行Array.ConvertAll(...' you can just do list.ConvertAll(e => e.ToString())。ToArray)`,只需键入更少的内容即可。
David

string.Join(“,”,list); 会做的很好:)
基督教徒

6

如果任何机构要转换自定义类对象列表而不是字符串列表,请使用类的csv行表示形式覆盖类的ToString方法。

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

myExampleCollection.Select改为MyClass.Select
Piotr Ferenc

5

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

        }
    }
}

附加信息:该进程无法访问文件'c:\ temp \ matchingMainWav.csv',因为它正在被另一个进程使用。该文件夹dev存在,但文件不存在...我没有使用该权限吗?
汤姆·斯蒂克

File.Create方法创建文件并在文件上打开FileStream。因此,您的文件已经打开。您实际上根本不需要file.Create方法:
David

如果任何属性为null,是否有办法解决?
丹尼尔·杰克逊

@DanielJackson您可以在此语句中编写where子句sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);未经测试,但不知道您要实现的目标
Ali Umair

4

我在这篇文章中对此进行了深入的解释。我将在代码中粘贴简短说明。

这是创建标题行的方法。它使用属性名称作为列名称。

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

1
这很好。我对此进行了改进,以将定界符作为参数传递,因此可以生成任何类型的定界文件。如果文本包含逗号,则很难处理CSV,因此我|使用改进的版本生成定界文件。谢谢!
湿婆


3

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

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

然后,您可以仅在原始列表上调用该方法:

string CsvString = myList.ToCsv();

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


2

String.Join的问题在于您不处理值中已经存在逗号的情况。如果存在逗号,则可以将“引号”中的值括起来,然后将所有现有引号替换为双引号。

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

请参阅CSV模块


2

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

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

2

无论出于何种原因,@ 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);

        }
    }
}


1

通用的ToCsv()扩展方法:

  • 支持Int16 / 32/64,float,double,decimal和任何支持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);
}

0

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

它提供任何定界符,在字符串中添加引号(如果它们包含定界符),并且交易将为空和空白。

    /// <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");
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.