【问题标题】:Better way to consume an IEnumerable<IEnumerable<string>>使用 IEnumerable<IEnumerable<string>> 的更好方法
【发布时间】:2011-03-29 20:08:32
【问题描述】:

我正在调用具有多个结果集(始终为 2 个)的存储过程,并将结果写入单独的文件(以管道分隔格式)。我无法将结果集拆分为单独的存储过程。我正在使用 IDataReader 和 IEnumerable 在此过程中保留尽可能少的内存。

有没有比使用 GetEnumerator/MoveNext/Current 获取内部 IEnumerable&lt;string&gt; 以传递给 File.AppendAllLines 更清洁的方式来使用我的 IEnumerable&lt;IEnumerable&lt;string&gt;&gt;

    public void Execute()
    {
        var reader = GetLines();

        using (var enumerator = reader.GetEnumerator())
        {
            enumerator.MoveNext();

            File.AppendAllLines("file1.dat", enumerator.Current);
            enumerator.MoveNext();

            File.AppendAllLines("file2.dat", enumerator.Current);
        }
    }

    public IEnumerable<IEnumerable<string>> GetLines()
    {
        Database db = DatabaseFactory.CreateDatabase("connectionStringKey");
        using (var command = db.GetStoredProcCommand("getdata_sp"))
        {
            var reader = db.ExecuteReader(command);
            yield return GetInnerEnumerable(reader);
            reader.NextResult();
            yield return GetInnerEnumerable(reader);
        }
    }

    private IEnumerable<string> GetInnerEnumerable(IDataReader reader)
    {
        while (reader.Read())
        {
            object[] rowValues = new object[reader.FieldCount];
            reader.GetValues(rowValues);
            yield return String.Join("|", rowValues);
        }
    }

【问题讨论】:

  • 也许我并不清楚命名方法 GetLines() ——它返回 IEnumerable>。应该叫它GetReaders()

标签: c# ienumerable coding-style


【解决方案1】:

为什么不使用foreach 循环?这是最基本的。

【讨论】:

  • 我需要传入文件名
  • 仔细查看代码。他将不得不编写两个 foreach,其中一个取所有不均匀计数,另一个取偶数计数。
【解决方案2】:

就个人而言,我只会使用带有单独变量的 foreach 循环来跟踪要写入的文件,例如:

public void Execute()
{
    var reader = GetLines();

    int i = 0;
    foreach (var inner in reader)
    {
        if (i % 2 == 0)
            File.AppendAllLines("file1.dat", inner);
        else
            File.AppendAllLines("file2.dat", inner);
        ++i;
    }
}

【讨论】:

  • 谢谢。我知道这是主观的,但我认为 foreach 和 counter 比 GetEnumerator/MoveNext/Current 方法更清晰/更易于理解。我想我希望有一个“Pop”/“Next”扩展方法(基本上是一个枚举器),也许是 Rx 的新东西。
【解决方案3】:

您可以使用SelectMany() 来展平枚举,因为您只对值本身感兴趣。

编辑:

根据评论SelectMany() 不适合给定用例,所以最好使用foreach 循环:

var reader = GetLines();
int index = 0;
foreach(var lines in reader)
    File.AppendAllLines(string.Format("file{0}.dat", index++%2 + 1), lines);

【讨论】:

  • 行将进入单独的文件,所以我认为我不能展平
【解决方案4】:

也许将GetLines() 的结果变成一个数组并通过索引访问它(因为你说总会有2个结果集)?

public void Execute()
{
    IEnumerable<string>[] rows = GetLines().ToArray();

    File.AppendAllLines("file1.dat", rows[0]);
    File.AppendAllLines("file2.dat", rows[1]);
}

【讨论】:

  • 好主意,但是当我运行它时,它并没有像预期的那样工作。由于在调用 .ToArray() 时调用 NextResult,因此当迭代第一个 Enumerable (rows[0]) 时,我们从 rows[1] 获取结果
  • @foson:啊,我没有考虑到这一点(我忘了NextResult() 出现在收益回报之间)。
【解决方案5】:

我只想将我的 GetLines 方法更改为以下

public IEnumerable<string> GetLines()
{
    Database db = DatabaseFactory.CreateDatabase("connectionStringKey");
    using (var command = db.GetStoredProcCommand("getdata_sp"))
    {
        var reader = db.ExecuteReader(command);
        for (var i = 0; i < 2; i++) 
        {  
          foreach(var cur in GetInnerEnumerable(reader))
          {
            yield return cur;
          }
          reader.NextResult();
        }
    }
}

让它返回 IEnumerable&lt;IEnumerable&lt;string&gt;&gt; 会给 API 的使用者带来不必要的负担。我的猜测是他们都更愿意将其视为IEnumerable&lt;string&gt;

【讨论】:

  • 这只是一些内部应用程序代码,不被任何其他代码使用。我怎么知道我的第一个阅读器已经结束,我应该开始将结果写入第二个文件?
  • @foson 你的问题特别说只有 2 个
  • 对,但是 Execute 方法如何知道 GetLines() 中的哪些值来自第一个读取器并保存在第一个文件中,哪些来自第二个读取器?
【解决方案6】:

IEnumerable 隐式支持foreach。所以:

public void Execute()
{
    var reader = GetLines();

    using (var enumerator = reader.GetEnumerator())
    {
        enumerator.MoveNext();

        File.AppendAllLines("file1.dat", enumerator.Current);
        enumerator.MoveNext();

        File.AppendAllLines("file2.dat", enumerator.Current);
    }
}

变成:

public void Execute()
{
    var reader = GetLines();

    int index = 0;

    foreach (string line in reader)
    {
        if ((index % 2) == 0)
            File.AppendAllLines("file1.dat", line);

        else
            File.AppendAllLines("file2.dat", line);

        index++;
    }
}

或者:

public void Execute()
{
    var reader = GetLines();

    var evenLines = reader.Where((str, i) => i % 2 == 0);
    var oddLines = reader.Where((str, i) => i % 2 != 0);

    foreach (string line in evenLines)
        File.AppendAllLines("file1.dat", line);

    foreach (string line in oddLines)
        File.AppendAllLines("file2.dat", line);
}

【讨论】:

  • 2个foreach的导致sp被执行两次
【解决方案7】:

您可以使用您的文件名将结果压缩到 Tuple&lt;&gt;,如下所示:

using System.Linq;
using FileZip = System.Tuple<
    System.String,
    System.Collections.Generic.IEnumerable<
        System.String>>;

public void Execute()
{
     var files = new string[] { "file1.dat", "file2.dat" };
     var results = GetLines();

     foreach (var file in files.Zip(results, (f, r) => new FileZip(f, r)))
     {
         File.AppendAllLines(file.Item1, file.Item2);
     }
}

当然,我很确定只要您返回不同数量的行,它就会抛出,但它会满足您的需求。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-17
    • 2020-01-18
    • 2019-03-17
    • 2011-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多