【问题标题】:Lazy load a list that is obtained from a using statement延迟加载从 using 语句获得的列表
【发布时间】:2021-03-12 14:23:06
【问题描述】:

我正在使用 CSVHelper 库来读取 CSV 文件。但这不是本文的目的

请参考下面的代码

public class Reader
{
    public IEnumerable<CSVModel> Read(string file)
    {
       using var reader = new StreamReader(@"C:\Users\z0042d8s\Desktop\GST invoice\RISK All RISKs_RM - Copy.CSV");
       using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
       IEnumerable<CSVModel> records = csv.GetRecords<CSVModel>();
       return records;
    }
}

上述方法中的 csv.GetRecords 使用 yield return 并在读取后立即返回每个 CSV 行,而不是等到读取整个 CSV 文件才返回(从 CSV 流数据)

我有一个消费者类,顾名思义,它使用 Read 方法返回的数据。


class Consumer
{
   public void Consume(IEnumerable<CSVModel> data)
   {
      foreach(var item in data)
      {
         //Do whatever you want with the data. I am gonna log it to the console
         Console.WriteLine(item);
      }
}


下面是调用者

   public static void main()
   {
      var data = new Reader().Read();
      new Consumer().Consume();
   }

希望我没有失去你。

我面临的问题如下

由于上面的数据变量是 IEnumerable,它将被延迟加载(换句话说,只要不迭代,它就不会读取 CSV 文件)。但是,当我调用 Consume() 方法时,该方法迭代数据变量,强制在 Read() 方法中读取 CSV 文件,使用语句中的 reader 和 csv 对象将被丢弃ObjectDisposed 异常。

另外,我不想删除 using 块之外的 reader 和 csv 对象,因为它们应该被丢弃以防止内存泄漏。

异常信息如下

    System.ObjectDisposedException: 'GetRecords<T>() returns an IEnumerable<T> 
    that yields records. This means that the method isn't actually called until 
    you try and access the values. e.g. .ToList() Did you create CsvReader inside 
    a using block and are now trying to access the records outside of that using 
    block?

而且我知道我可以使用贪婪运算符 (.ToList())。但我希望延迟加载工作。

如果有什么出路,请提出建议。

提前致谢。

【问题讨论】:

  • 不是 using 关键字应该定义一个代码块吗? using(var x = ) { /* code in scope */ }
  • @SteveB 从 C# 8 开始,不再是。 C# 8 引入了using declarations,它只是一些自动生成 using 块的语法糖,让您不再需要。
  • @MindSwipe:很高兴知道。这意味着在外部范围的末尾隐式调用了被处置的?
  • @SteveB 是正确的。

标签: c# .net


【解决方案1】:

您可以将操作作为参数传递给阅读器。它改变了一些方法的想法:

public class Reader
{
    public void Read(string file, Action<CSVModel> action)
    {
        using var reader = new StreamReader(@"C:\Users\z0042d8s\Desktop\GST invoice\RISK All RISKs_RM - Copy.CSV");
        using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
        
        IEnumerable<CSVModel> records = csv.GetRecords<CSVModel>();

        foreach(var record in records){
            action(record);
        }
    }
}

class Consumer
{
    public void Consume(CSVModel data)
    {
        //Do whatever you want with the data. I am gonna log it to the console
        Console.WriteLine(item);
    }
}

public static void Main()
{
    var consumer = new Consumer();
    new Reader().Read(consumer.Consume); // Pass the action here 
}

或者,您可以将整个 Reader 类设为 Disposable :

public class Reader : IDisposable
{
    private readonly StreamReader _reader;
    private readonly CsvReader _csv;

    public Reader(string file)
    {
        _reader = new StreamReader(file);
        _csv = new CsvReader(_reader, CultureInfo.InvariantCulture);
    }

    public IEnumerable<CSVModel> Read()
    {
        return csv.GetRecords<CSVModel>();                   
    }

    public void Dispose() => Dispose(true);

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        if (disposing)
        {
           _csv.Dispose();
           _reader.Dispose();
        }

        _disposed = true;
    }

}

class Consumer
{
    public void Consume(IEnumerable<CSVModel> data)
    {
        foreach(var item in data)
        {
             //Do whatever you want with the data. I am gonna log it to the console
             Console.WriteLine(item);
        }
    }
}

public static void Main()
{
    using var myReader = new Reader("c:\\path.csv");
    var consumer = new Consumer().Consume(myReader.Read());
}

【讨论】:

  • 哦,太棒了。
【解决方案2】:

您可以像这样从 GetRecords IEnumerable 中懒惰地枚举项目并将记录生成给消费者:

public class Reader
{
    public IEnumerable<CSVModel> Read(string file)
    {
        using var reader = new StreamReader(@"C:\Users\z0042d8s\Desktop\GST invoice\RISK All RISKs_RM - Copy.CSV");
        using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);

        foreach (var csvRecord in csv.GetRecords<CSVModel>())
        {
            yield return csvRecord;
        }
    }
}

这样您就可以保证在处理基础数据之前枚举记录,并且您不需要预先加载所有数据。

【讨论】:

  • 成功了!当我看到这个评论时,我不相信它会起作用。但是当我实施它时,它确实做到了。意识到如果函数具有 yield return 关键字,则函数内部的代码在迭代之前不会执行。太棒了!
  • @VarunShridhar 您可能需要考虑“接受”这些答案之一,以便用户获得积分
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-22
  • 1970-01-01
  • 2020-11-05
  • 1970-01-01
  • 2012-03-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多