如何使用C#创建CSV Excel文件?

131

我正在寻找一个用于创建CSV Excel文件的类。

期望功能:

  • 极其简单易用
  • 转义逗号和引号以便Excel可以正确处理
  • 导出日期和日期时间格式,且不受时区影响

您知道有哪些类可以实现这个功能吗?


12
最好在“问题”部分提出问题,然后在“答案”部分发布您自己的答案。请确保在问题中添加标签和关键词以使其可搜索。 - Cheeso
重要提示:当“value”中存在回车符时,您还应添加引号。 - Alex from Jitbit
谢谢@Chris,如果我可以提个建议的话,这段代码可能会抛出KeyNotFoundException异常,请看我的回答。 - Jose Rodriguez
最好的例子...但是我怎么能在单个文件中添加两个表呢,我的意思是我有一个两行的表和另一个有10行的表,它们都有唯一的列名。我想把两行的表添加到顶部,在间隔两行后再添加第二个表。 - Floki
13个回答

92

我使用反射编写了一个稍微不同的版本,以满足我的需求。我需要将对象列表导出到csv文件中。如果有人想在将来使用它,请参考以下内容。

public class CsvExport<T> where T: class
    {
        public List<T> Objects;

        public CsvExport(List<T> objects)
        {
            Objects = objects;
        }

        public string Export()
        {
            return Export(true);
        }

        public string Export(bool includeHeaderLine)
        {

            StringBuilder sb = new StringBuilder();
            //Get properties using reflection.
            IList<PropertyInfo> propertyInfos = typeof(T).GetProperties();

            if (includeHeaderLine)
            {
                //add header line.
                foreach (PropertyInfo propertyInfo in propertyInfos)
                {
                    sb.Append(propertyInfo.Name).Append(",");
                }
                sb.Remove(sb.Length - 1, 1).AppendLine();
            }

            //add value for each property.
            foreach (T obj in Objects)
            {               
                foreach (PropertyInfo propertyInfo in propertyInfos)
                {
                    sb.Append(MakeValueCsvFriendly(propertyInfo.GetValue(obj, null))).Append(",");
                }
                sb.Remove(sb.Length - 1, 1).AppendLine();
            }

            return sb.ToString();
        }

        //export to a file.
        public void ExportToFile(string path)
        {
            File.WriteAllText(path, Export());
        }

        //export as binary data.
        public byte[] ExportToBytes()
        {
            return Encoding.UTF8.GetBytes(Export());
        }

        //get the csv value for field.
        private string MakeValueCsvFriendly(object value)
        {
            if (value == null) return "";
            if (value is Nullable && ((INullable)value).IsNull) return "";

            if (value is DateTime)
            {
                if (((DateTime)value).TimeOfDay.TotalSeconds == 0)
                    return ((DateTime)value).ToString("yyyy-MM-dd");
                return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss");
            }
            string output = value.ToString();

            if (output.Contains(",") || output.Contains("\""))
                output = '"' + output.Replace("\"", "\"\"") + '"';

            return output;

        }
    }

使用示例:(根据评论进行了更新)

CsvExport<BusinessObject> csv= new CsvExport<BusinessObject>(GetBusinessObjectList());
Response.Write(csv.Export());

5
更合适的表述是: 这段代码的意思是:创建了一个名为 x 的 BusinessObject 列表,以及一个 CsvExport<BusinessObject> 对象 x,该对象使用 MUsers 参数进行初始化。 - hidden
5
你的 INullable 接口是从哪里来的? - Kilhoffer
最好的例子...但我怎么在一个文件中添加两个表格,也就是说,我有一个包含两行的表格和另一个包含10行的表格,两者都有唯一的列名。我想把两行的表格放在顶部,在间隔两行后添加第二个表格。 - Floki
2
我知道原帖是2011年的,所以我不确定当时使用的.NET版本是否支持。但为什么不删除public string Export()方法并将另一个方法更改为public string Export(bool includeHeaderLiner = true)(带有默认参数值)。再次强调,我不确定默认参数是否在2011年可用,但当前代码对我来说看起来不太规范。 - Kevin Cruijssen

19

请原谅我。

但我认为公开的开源代码库是分享代码、进行修正和添加的更好方式,比如“我修复了这个,我修复了那个”之类的。

因此,我将主题发起人的代码和所有添加内容制作成了一个简单的git仓库:

https://github.com/jitbit/CsvExport

我也自己添加了一些有用的修复。每个人都可以添加建议,分支进行贡献等等。将您的分支发送给我,以便我将它们合并回仓库中。

附注:我已经发布了Chris的所有版权声明。如果您反对这个想法,请告诉我,我会撤销它。


11

另一个读写CSV文件的好解决方案是FileHelpers(开源)。


注意:Excel 支持仅用于基本场景:当前实现的 Excel 支持仅适用于基本场景。如果您需要自定义格式、图表等内容,则必须使用自定义代码。强烈建议直接使用 NPOI 库。 - A K

6
如果有人想把这个转换为IEnumerable的扩展方法,可以使用以下方法:
public static class ListExtensions
{
    public static string ExportAsCSV<T>(this IEnumerable<T> listToExport, bool includeHeaderLine, string delimeter)
    {
        StringBuilder sb = new StringBuilder();

        IList<PropertyInfo> propertyInfos = typeof(T).GetProperties();

        if (includeHeaderLine)
        {
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                sb.Append(propertyInfo.Name).Append(",");
            }
            sb.Remove(sb.Length - 1, 1).AppendLine();
        }

        foreach (T obj in listToExport)
        {
            T localObject = obj;

            var line = String.Join(delimeter, propertyInfos.Select(x => SanitizeValuesForCSV(x.GetValue(localObject, null), delimeter)));

            sb.AppendLine(line);
        }

        return sb.ToString();
    }

    private static string SanitizeValuesForCSV(object value, string delimeter)
    {
        string output;

        if (value == null) return "";

        if (value is DateTime)
        {
            output = ((DateTime)value).ToLongDateString();
        }
        else
        {
            output = value.ToString();                
        }

        if (output.Contains(delimeter) || output.Contains("\""))
            output = '"' + output.Replace("\"", "\"\"") + '"';

        output = output.Replace("\n", " ");
        output = output.Replace("\r", "");

        return output;
    }
}

6
使用string.Join代替所有的foreach循环怎么样?

String.Join只能用于string[],而我正在使用List<string>的一些功能。 - Chris
12
String.Join(",", List<string>) 也可以使用。 - Rafael Herscovici

5
“这个类做得非常好。简单易用。我修改了这个类,把导出的第一行加入了标题,想着跟大家分享一下:”
“使用:”
CsvExport myExport = new CsvExport();
myExport.addTitle = String.Format("Name: {0},{1}", lastName, firstName));

类:

public class CsvExport
{
    List<string> fields = new List<string>();

    public string addTitle { get; set; } // string for the first row of the export

    List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>();
    Dictionary<string, object> currentRow
    {
        get
        {
            return rows[rows.Count - 1];
        }
    }

    public object this[string field]
    {
        set
        {
            if (!fields.Contains(field)) fields.Add(field);
            currentRow[field] = value;
        }
    }

    public void AddRow()
    {
        rows.Add(new Dictionary<string, object>());
    }

    string MakeValueCsvFriendly(object value)
    {
        if (value == null) return "";
        if (value is Nullable && ((INullable)value).IsNull) return "";
        if (value is DateTime)
        {
            if (((DateTime)value).TimeOfDay.TotalSeconds == 0)
                return ((DateTime)value).ToString("yyyy-MM-dd");
            return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss");
        }
        string output = value.ToString();
        if (output.Contains(",") || output.Contains("\""))
            output = '"' + output.Replace("\"", "\"\"") + '"';
        return output;

    }

    public string Export()
    {
        StringBuilder sb = new StringBuilder();

        // if there is a title
        if (!string.IsNullOrEmpty(addTitle))
        {
            // escape chars that would otherwise break the row / export
            char[] csvTokens = new[] { '\"', ',', '\n', '\r' };

            if (addTitle.IndexOfAny(csvTokens) >= 0)
            {
                addTitle = "\"" + addTitle.Replace("\"", "\"\"") + "\"";
            }
            sb.Append(addTitle).Append(",");
            sb.AppendLine();
        }


        // The header
        foreach (string field in fields)
        sb.Append(field).Append(",");
        sb.AppendLine();

        // The rows
        foreach (Dictionary<string, object> row in rows)
        {
            foreach (string field in fields)
                sb.Append(MakeValueCsvFriendly(row[field])).Append(",");
            sb.AppendLine();
        }

        return sb.ToString();
    }

    public void ExportToFile(string path)
    {
        File.WriteAllText(path, Export());
    }

    public byte[] ExportToBytes()
    {
        return Encoding.UTF8.GetBytes(Export());
    }
}

4

3

我添加了ExportToStream函数,这样csv文件就不必先保存到硬盘上了。

public Stream ExportToStream()
{
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(Export(true));
    writer.Flush();
    stream.Position = 0;
    return stream;
}

3
我已经添加了。
public void ExportToFile(string path, DataTable tabela)
{

     DataColumnCollection colunas = tabela.Columns;

     foreach (DataRow linha in tabela.Rows)
     {

           this.AddRow();

           foreach (DataColumn coluna in colunas)

           {

               this[coluna.ColumnName] = linha[coluna];

           }

      }
      this.ExportToFile(path);

}

以前的代码不适用于旧的.NET版本。对于3.5版本的框架,请使用另一个版本:

        public void ExportToFile(string path)
    {
        bool abort = false;
        bool exists = false;
        do
        {
            exists = File.Exists(path);
            if (!exists)
            {
                if( !Convert.ToBoolean( File.CreateText(path) ) )
                        abort = true;
            }
        } while (!exists || abort);

        if (!abort)
        {
            //File.OpenWrite(path);
            using (StreamWriter w = File.AppendText(path))
            {
                w.WriteLine("hello");
            }

        }

        //File.WriteAllText(path, Export());
    }

2

非常感谢!我修改了这个类:

  • 使用变量分隔符,而不是在代码中硬编码
  • 替换MakeValueCsvFriendly中的所有换行符(\n \r \n\r)

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.SqlTypes;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

    public class CsvExport
    {

        public char delim = ';';
        /// <summary>
        /// To keep the ordered list of column names
        /// </summary>
        List<string> fields = new List<string>();

        /// <summary>
        /// The list of rows
        /// </summary>
        List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>();

        /// <summary>
        /// The current row
        /// </summary>
        Dictionary<string, object> currentRow { get { return rows[rows.Count - 1]; } }

        /// <summary>
        /// Set a value on this column
        /// </summary>
        public object this[string field]
        {
            set
            {
                // Keep track of the field names, because the dictionary loses the ordering
                if (!fields.Contains(field)) fields.Add(field);
                currentRow[field] = value;
            }
        }

        /// <summary>
        /// Call this before setting any fields on a row
        /// </summary>
        public void AddRow()
        {
            rows.Add(new Dictionary<string, object>());
        }

        /// <summary>
        /// Converts a value to how it should output in a csv file
        /// If it has a comma, it needs surrounding with double quotes
        /// Eg Sydney, Australia -> "Sydney, Australia"
        /// Also if it contains any double quotes ("), then they need to be replaced with quad quotes[sic] ("")
        /// Eg "Dangerous Dan" McGrew -> """Dangerous Dan"" McGrew"
        /// </summary>
        string MakeValueCsvFriendly(object value)
        {
            if (value == null) return "";
            if (value is INullable && ((INullable)value).IsNull) return "";
            if (value is DateTime)
            {
                if (((DateTime)value).TimeOfDay.TotalSeconds == 0)
                    return ((DateTime)value).ToString("yyyy-MM-dd");
                return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss");
            }
            string output = value.ToString();
            if (output.Contains(delim) || output.Contains("\""))
                output = '"' + output.Replace("\"", "\"\"") + '"';
            if (Regex.IsMatch(output,  @"(?:\r\n|\n|\r)"))
                output = string.Join(" ", Regex.Split(output, @"(?:\r\n|\n|\r)"));
            return output;
        }

        /// <summary>
        /// Output all rows as a CSV returning a string
        /// </summary>
        public string Export()
        {
            StringBuilder sb = new StringBuilder();

            // The header
            foreach (string field in fields)
                sb.Append(field).Append(delim);
            sb.AppendLine();

            // The rows
            foreach (Dictionary<string, object> row in rows)
            {
                foreach (string field in fields)
                    sb.Append(MakeValueCsvFriendly(row[field])).Append(delim);
                sb.AppendLine();
            }

            return sb.ToString();
        }

        /// <summary>
        /// Exports to a file
        /// </summary>
        public void ExportToFile(string path)
        {
            File.WriteAllText(path, Export());
        }

        /// <summary>
        /// Exports as raw UTF8 bytes
        /// </summary>
        public byte[] ExportToBytes()
        {
            return Encoding.UTF8.GetBytes(Export());

        }

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接