【问题标题】:Importing CSV data into C# classes将 CSV 数据导入 C# 类
【发布时间】:2011-12-03 21:25:30
【问题描述】:

我知道如何读取和显示 .csv 文件的一行。现在我想解析该文件,将其内容存储在数组中,并将这些数组用作我创建的某些类的值。

不过我想了解一下。

这是一个例子:

basketball,2011/01/28,Rockets,Blazers,98,99
baseball,2011/08/22,Yankees,Redsox,4,3

如您所见,每个字段都用逗号分隔。我创建了作为 Sport.cs 类的扩展的 Basketball.cs 和 Baseball 类,它具有以下字段:

private string sport;
private string date;
private string team1;
private string team2;
private string score;

我知道这很简单,并且有更好的方法来存储此信息,即为每个团队创建类,将日期设为 DateType 数据类型,等等,但我想知道如何输入信息进入班级。

我假设这与 getter 和 setter 有关...我也读过字典和集合,但我想通过将它们全部存储在数组中来开始简单...(如果这使得感觉...随时纠正我)。

这是我目前所拥有的。它所做的只是读取 csv 并在控制台上模仿其内容:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Assign01
{
    class Program
    {
        static void Main(string[] args)
        {
            string line;
            FileStream aFile = new FileStream("../../sportsResults.csv", FileMode.Open);
            StreamReader sr = new StreamReader(aFile);

            // read data in line by line
            while ((line = sr.ReadLine()) != null)
            {
                Console.WriteLine(line);
                line = sr.ReadLine();
            }
            sr.Close();
        }
    }
}

我们将不胜感激。

【问题讨论】:

    标签: c# class csv import getter-setter


    【解决方案1】:

    对于弹性、快速和省力的解决方案,您可以使用CsvHelper,它可以处理大量代码和边缘情况,并且具有相当不错的documentation

    首先,安装CsvHelper package on Nuget

    a) CSV 带有标题

    如果你的 csv 有这样的标题:

    sport,date,team 1,team 2,score 1,score 2
    basketball,2011/01/28,Rockets,Blazers,98,99
    baseball,2011/08/22,Yankees,Redsox,4,3
    

    您可以向您的类添加属性以将字段名称映射到您的类名称,如下所示:

    public class SportStats
    {
        [Name("sport")]
        public string Sport { get; set; }
        [Name("date")]
        public DateTime Date { get; set; }
        [Name("team 1")]
        public string TeamOne { get; set; }
        [Name("team 2")]
        public string TeamTwo { get; set; }
        [Name("score 1")]
        public int ScoreOne { get; set; }
        [Name("score 2")]
        public int ScoreTwo { get; set; }
    }
    

    然后像这样调用:

    List<SportStats> records;
    
    using (var reader = new StreamReader(@".\stats.csv"))
    using (var csv = new CsvReader(reader))
    {
        records = csv.GetRecords<SportStats>().ToList();
    }
    

    b) CSV 没有标题

    如果您的 csv 没有这样的标题:

    basketball,2011/01/28,Rockets,Blazers,98,99
    baseball,2011/08/22,Yankees,Redsox,4,3
    

    您可以将属性添加到您的类并按顺序按位置映射到 CSV,如下所示:

    public class SportStats
    {
        [Index(0)]
        public string Sport { get; set; }
        [Index(1)]
        public DateTime Date { get; set; }
        [Index(2)]
        public string TeamOne { get; set; }
        [Index(3)]
        public string TeamTwo { get; set; }
        [Index(4)]
        public int ScoreOne { get; set; }
        [Index(5)]
        public int ScoreTwo { get; set; }
    }
    

    然后像这样调用:

    List<SportStats> records;
    
    using (var reader = new StreamReader(@".\stats.csv"))
    using (var csv = new CsvReader(reader))
    {
        csv.Configuration.HasHeaderRecord = false;
        records = csv.GetRecords<SportStats>().ToList();
    }
    

    进一步阅读

    【讨论】:

      【解决方案2】:

      创建数组来保存信息不是一个好主意,因为您不知道输入文件中有多少行。您的 Array 的初始大小是多少?我建议您使用例如通用列表来保存信息(例如列表)。

      您还可以将构造函数添加到接受数组的 Sport 类(如上述答案中所述的拆分操作的结果。

      此外,您还可以在设置器中提供一些转换

      public class Sport
      {
          private string sport;
          private DateTime date;
          private string team1;
          private string team2;
          private string score;
      
          public Sport(string[] csvArray)
          {
              this.sport = csvArray[0];
              this.team1 = csvArray[2];
              this.team2 = csvArray[3];
              this.date = Convert.ToDateTime(csvArray[1]);
              this.score = String.Format("{0}-{1}", csvArray[4], csvArray[5]);
          }
      

      为了简单起见,我编写了转换方法,但请记住,这也不是一种非常安全的方法,除非您确定 DateField 始终包含有效的日期并且 Score 始终包含数值。您可以尝试其他更安全的方法,例如 tryParse 或一些异常处理。

      老实说,必须补充一点,上述解决方案很简单(根据要求),在概念层面上我建议不要这样做。将属性和 csv 文件之间的映射逻辑放在类中会使运动类过于依赖文件本身,因此可重用性降低。以后对文件结构的任何更改都应反映在您的类中,并且通常会被忽略。因此,将您的“映射和转换”逻辑放在主程序中并保持您的类尽可能干净会更明智

      (通过将“分数”问题格式化为 2 个字符串和一个连字符来更改您的问题)

      【讨论】:

      • 我不会真正将分数作为整数,因为它是两个整数相互比较......即 99-98。但我真的很感激这个答案。谢谢。
      【解决方案3】:

      将字符串拆分为数组以获取数据可能容易出错且速度慢。尝试使用 OLE 数据提供程序来读取 CSV,就好像它是 SQL 数据库中的表一样,这样您就可以使用 WHERE 子句来过滤结果。

      App.Config

      <?xml version="1.0" encoding="utf-8" ?>
      <configuration>
        <connectionStrings>
          <add name="csv" providerName="System.Data.OleDb" connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source='C:\CsvFolder\';Extended Properties='text;HDR=Yes;FMT=Delimited';" />
        </connectionStrings>
      </configuration>
      

      program.cs

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Data.OleDb;
      using System.Configuration;
      using System.Data;
      using System.Data.Common;
      
      namespace CsvImport
      {
          class Stat
          {
              public string Sport { get; set; }
              public DateTime Date { get; set; }
              public string TeamOne { get; set; }
              public string TeamTwo { get; set; }
              public int Score { get; set; }
          }
      
          class Program
          {
              static void Main(string[] args)
              {
                  ConnectionStringSettings csv = ConfigurationManager.ConnectionStrings["csv"];
                  List<Stat> stats = new List<Stat>();
      
                  using (OleDbConnection cn = new OleDbConnection(csv.ConnectionString))
                  {
                      cn.Open();
                      using (OleDbCommand cmd = cn.CreateCommand())
                      {
                          cmd.CommandText = "SELECT * FROM [Stats.csv]";
                          cmd.CommandType = CommandType.Text;
                          using (OleDbDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                          {
                              int fieldSport = reader.GetOrdinal("sport");
                              int fieldDate = reader.GetOrdinal("date");
                              int fieldTeamOne = reader.GetOrdinal("teamone");
                              int fieldTeamTwo = reader.GetOrdinal("teamtwo");
                              int fieldScore = reader.GetOrdinal("score");
      
                              foreach (DbDataRecord record in reader)
                              {
                                  stats.Add(new Stat
                                  {
                                      Sport = record.GetString(fieldSport),
                                      Date = record.GetDateTime(fieldDate),
                                      TeamOne = record.GetString(fieldTeamOne),
                                      TeamTwo = record.GetString(fieldTeamTwo),
                                      Score = record.GetInt32(fieldScore)
                                  });
                              }
                          }
                      }
                  }
      
                  foreach (Stat stat in stats)
                  {
                      Console.WriteLine("Sport: {0}", stat.Sport);
                  }
              }
          }
      }
      

      这是 csv 的外观

      stats.csv

      sport,date,teamone,teamtwo,score
      basketball,28/01/2011,Rockets,Blazers,98
      baseball,22/08/2011,Yankees,Redsox,4
      

      【讨论】:

      • 这比我的理解有点超前,我试图从简单开始,但我仍然很感激。
      【解决方案4】:

      虽然有很多库可以让 csv 读取变得容易(请参阅:here),但您现在需要做的就是拆分它。

      String[] csvFields = line.Split(",");
      

      现在将每个字段分配给适当的成员

      sport = csvFields[0];
      date = csvFields[1];
      //and so on
      

      然而,这将在您每次读取新行时覆盖值,因此您需要将值打包到一个类中并将该类的实例保存到一个列表中。

      【讨论】:

      • 当你说将值打包到一个类中,并将实例保存到一个列表中,你到底是什么意思?
      • 查看 user936598 对课程外观的评论。然后在进入 lop 之前创建一个List&lt;Sport&gt; sports = new List&lt;Sport&gt;() 并在while - 循环中使用sports.Add(new Sport(line.split(","))); 添加到它。
      【解决方案5】:
      // use "Microsoft.VisualBasic.dll"
      
      using System;
      using Microsoft.VisualBasic.FileIO;
      
      class Program {
          static void Main(string[] args){
              using(var csvReader = new TextFieldParser(@"sportsResults.csv")){
                  csvReader.SetDelimiters(new string[] {","});
                  string [] fields;
                  while(!csvReader.EndOfData){
                      fields = csvReader.ReadFields();
                      Console.WriteLine(String.Join(",",fields));//replace make instance
                  }
              }
          }
      }
      

      【讨论】:

        【解决方案6】:

        Linq 对此也有解决方案,您可以将输出定义为列表或数组。在下面的例子中有一个类作为数据和数据类型的定义。

        var modelData = File.ReadAllLines(dataFile)
                           .Skip(1)
                           .Select(x => x.Split(','))
                           .Select(dataRow => new TestModel
                           {
                               Column1 = dataRow[0],
                               Column2 = dataRow[1],
                               Column3 = dataRow[2],
                               Column4 = dataRow[3]
                           }).ToList(); // Or you can use .ToArray()
        

        【讨论】:

          【解决方案7】:

          以下是大多数新手喜欢尝试和错误的新手和引人注目的解决方案 请不要忘记在引用中添加 System.Core.dll 在 .cs 文件中导入命名空间:使用 System.Linq;

          也许添加迭代器会是更好的代码

          private static IEnumerable<String> GetDataPerLines()
          {
              FileStream aFile = new FileStream("sportsResults.csv",FileMode.Open);             
              StreamReader sr = new StreamReader(aFile); 
              while ((line = sr.ReadLine()) != null)             
              { 
                  yield return line;
              }             
              sr.Close(); 
          }
          
          static void Main(string[] args)
          {
              var query = from data in GetDataPerLines()
                    let splitChr = data.Split(",".ToCharArray())
                          select new Sport
              {
                 sport = splitChr[0],
                 date = splitChr[1],.. and so on
              }
          
              foreach (var item in query)
              {
                  Console.Writeline(" Sport = {0}, in date when {1}",item.sport,item.date);
              }
          }
          

          也许像这样,上面的示例正在使用 yield 创建您自己的迭代(请查看 MSDN 文档)并根据您的字符串创建集合。

          如果我写错了代码,请告诉我,因为我写答案时没有 Visual Studio。 据您所知,像“Sport[]”这样的一维数组将转换为 CLR IEnumerable

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-12-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-30
            • 2011-08-09
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多