【问题标题】:Converting a generic list to a CSV string将通用列表转换为 CSV 字符串
【发布时间】:2010-12-25 19:04:42
【问题描述】:

我有一个整数值列表 (List),并希望生成一串以逗号分隔的值。这是列表中的所有项目输出到单个逗号分隔列表。

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

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

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

【问题讨论】:

    标签: c# generics .net-3.5


    【解决方案1】:

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

    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&lt;T&gt;(string, IEnumerable&lt;T&gt;)。此方法会自动将每个元素x 投影到x.ToString()

    【讨论】:

    • List&lt;int&gt; 在框架 3.5 中没有方法 Select,除非我遗漏了什么。
    • @ajeh:您可能缺少using 声明。
    • 具体导入哪个?
    • 试试System.Linq.Enumerable(当然你需要System.Core.dll 程序集,但大概你已经拥有了)。你看,List&lt;int&gt; neverSelect 作为方法。相反,System.Linq.EnumerableSelect 定义为IEnumerable&lt;T&gt; 的扩展方法,List&lt;int&gt; 就是其中的一个示例。因此,您需要在导入中使用 System.Linq.Enumerable 来选择此扩展方法。
    • 你的答案都不适合我。我只得到一行逗号分隔的字符串,其中仅包含通用列表的类型名称
    【解决方案2】:

    您可以使用String.Join

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

    【讨论】:

    • 这里不需要在调用ConvertAll 时指定泛型类型参数——intstring 都会被推断出来。
    • 而不是 Array.ConvertAll(...' you can just do list.ConvertAll(e=>e.ToString()).ToArray)`,只是更少的输入。
    • string.Join(",", list);会很好的:)
    【解决方案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】:

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

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

      【讨论】:

      • ToArray()List&lt;int&gt; 方法不能与框架 3.5 中的类型参数一起使用,除非我遗漏了什么。
      • 太棒了。不需要 ToArray,因为使用了子 ToString()。
      【解决方案5】:

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

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

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

      【讨论】:

      • 不能覆盖类的ToString,使用上面的方法吗?
      【解决方案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
      【解决方案7】:

      由于@Frank Create a CSV File from a .NET Generic List 给出的链接中的代码存在以, 结束每一行的小问题,我修改了代码以摆脱它。希望它对某人有所帮助。

      /// <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 方法:
      • 如果任何属性为空,有没有办法解决?
      • @DanielJackson 您可以在此语句中编写 where 子句sw.Write(string.Join(",", props.Select(d =&gt; d.Name).ToArray()) + newLine); 未测试但不知道您要达到的目标
      【解决方案8】:

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

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

      那么就可以直接调用原列表中的方法了:

      string CsvString = myList.ToCsv();
      

      比其他一些建议更简洁易读。

      【讨论】:

        【解决方案9】:

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

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

        我大量使用他的技术来编写文件以进行归档,效果很好。

        【讨论】:

          【解决方案10】:

          String.Join 的问题在于您没有处理值中已经存在的逗号的情况。当逗号存在时,您将值括在引号中,并将所有现有的引号替换为双引号。

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

          CSV Module

          【讨论】:

            【解决方案11】:

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

            【讨论】:

            • 这很好用。我对此进行了改进,将分隔符作为参数传递,因此可以生成任何类型的分隔文件。如果文本包含逗号,则 CSV 很难处理,因此我使用改进的版本生成 | 分隔文件。谢谢!
            • 但是如果您仍然喜欢逗号分隔,但数据可能包含逗号,则输入 sw.Write("\"" + prop.GetValue(item) + "\",");和类似的标题将解决这个问题。
            【解决方案12】:

            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 actorList = csv.GetRecords();

            写入 CSV 文件。

            using (var csv = new CsvWriter( new StreamWriter( "Actors.csv" ) )) 
            {
                csv.WriteRecords( actorsList );
            }
            

            【讨论】:

              【解决方案13】:

              一个通用的 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);
              }
              

              【讨论】:

                【解决方案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);
                
                        }
                    }
                }
                

                【讨论】:

                  【解决方案15】:

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

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

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

                  【讨论】:

                    猜你喜欢
                    • 2018-08-04
                    • 2020-01-03
                    • 2017-11-07
                    • 2016-06-24
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-07-11
                    相关资源
                    最近更新 更多