【问题标题】:Variable initalisation in while loopwhile循环中的变量初始化
【发布时间】:2011-05-24 19:30:05
【问题描述】:

我有一个分块读取文件的函数。

public static DataObject ReadNextFile(){ ...}

数据对象看起来像这样:

public DataObject
{
   public string Category { get; set; }

   // And other members ...
}

我想要做的基本上是以下

List<DataObject> dataObjects = new List<DataObject>();

while(ReadNextFile().Category == "category")
{
   dataObjects.Add(^^^^^ the thingy in the while);
}

我知道这可能不是它的完成方式,因为我如何访问我刚刚阅读的对象。

【问题讨论】:

  • 你确定,你想问什么?
  • 我很难弄清楚你到底想要什么。
  • 很抱歉,我意识到我的问题非常含糊。这就是我如何访问 while(ReadNextElement()) 中的对象:p
  • 重新循环逻辑,这真的是你想做的吗?您不想从文件中读取所有“匹配”的 DataObjects,无论中间是否有其他数据对象?
  • @Timo:仅作记录:我是否正确地暗示您的真实DataObject 包含的属性不仅仅是您正在测试的类别吗?

标签: c# initialization while-loop


【解决方案1】:

这是主观的,但我讨厌这种模式(我完全认识到我在这里是极少数)。当我需要这样的东西时,我就是这样做的。

var dataObjects = new List<DataObject>();
while(true) {
    DataObject obj = ReadNextFile();
    if(obj.Category != "category") {
        break;
    }
    dataObjects.Add(obj);
}

但是这些天,最好说

List<DataObject> dataObjects = GetItemsFromFile(path)
                                   .TakeWhile(x => x.Category == "category")
                                   .ToList();

当然,这里GetItemsFromFile 会从path 指向的文件中读取项目并返回IEnumerable&lt;DataObject&gt;

【讨论】:

  • @Saeed:你是什么意思“因为这不可能?”可以肯定的是,这是一种奇怪的行为。
  • @Saeed:这是一个没有回答问题的答案。在我看来,这使它成为一个糟糕的答案。然而,这个答案——你投反对票的那个——确实回答了这个问题。除了因为您对其他人对您的答案投反对票感到恼火之外,您能否给出 任何 反对它的理由?
  • @Saeed:为什么“LINQ 不是答案?”你是什​​么意思“第一个没有解决OPs 签名”(原文如此)。
  • @Saeed:在保留while(ReadNextFile().Category == "category") 部分(顺便说一句,这不是签名)的同时,无法回答所写的问题。我看到你也假设这个答案的赞成票是由于友谊而不是仅仅同意答案......而且你也假设反对票投反对你对应这个答案的赞成票。他们在我的情况下是这样,但你不应该假设其他人也是如此。
  • @Saeed:嗯,我代表其他人,这就是重点 - 而你假设动机支持这个答案和否决你的。
【解决方案2】:
List<DataObject> dataObjects = new List<DataObject>();
string category = "";

while((category=ReadNextFile().Category) == "category")
{
   dataObjects.Add(new DataObject{Category = category});
}

如果你有更复杂的对象,你可以这样做(比如 jon):

List<DataObject> dataObjects = new List<DataObject>();
var category = new DataObject();

while((category=ReadNextFile()).Category == "category")
{
   dataObjects.Add(category);
}

【讨论】:

  • @Saeed:抱歉,我之前的评论显然没有成功——我的 3G 网络连接不稳定。这不会编译,因为您正在尝试将字符串添加到List&lt;DataObject&gt;。这个想法不是记住最后一个category,而是记住最后一个DataObject。请参阅我对固定版本的回答。
  • @Jon Skeet,我已经编辑了答案,谢谢,我没有考虑我只想展示OP 想要的方式的列表类型。
  • @Saeed:仍然是 -1,因为虽然我们看到的只是 Category 属性,但在现实生活中,我希望对象中会有更多数据......您当前的代码。正如 OP 所说,他想记住 他刚刚阅读的对象,而不是从他正在过滤的一点信息中重建它。这并不难 - 看看我的答案。
  • (还请记住,您在编辑答案和期望删除所有反对票之间只剩下 2 分钟。我碰巧在看这个问题,但不是每个人都会......)考虑到您的答案的先前版本不会编译,即使现在它也不是真正要求的,我会说 5 次赞成和 3 次反对非常慷慨;)
  • 6 年后,它仍然是一个糟糕的答案。第一个代码 sn -p DOES NOT DO WHAT OP ASKED。事实上,它不会做任何任何人 可能想要做的事情。这使得试图理解这个答案是浪费时间。第二个代码 sn-p 执行所要求的操作,但写得不好:(1)变量category 命名不当,因为它不再包含类别。 (2) 变量category被初始化为一个默认的DataObject。为什么?在这种情况下,它根本不需要初始化,因为它只在循环中使用。如果有的话,将其初始化为 null。
【解决方案3】:

您应该考虑在调用 ReadNextFile() 的类容器上实现 IEnumerator。然后,您将始终使用 IEnumerator.Current 引用当前对象,并且 MoveNext() 将返回您正在寻找的布尔值以检查进度。像这样的:

public class ObjectReader : IEnumerator<DataObject>
{
    public bool MoveNext()
    {
       // try to read next file, return false if you can't
       // if you can, set the Current to the returned DataObject
    }

    public DataObject Current
    {
        get;
        private set;
    }
}

【讨论】:

  • 你不需要自己去实现它的所有麻烦 - 使用迭代器块代替。他们摇滚:)
  • 我同意您不需要这样做,但如果这是该对象的目的(看起来确实如此),那么 IMO 似乎是一个更优雅的解决方案。
  • @poindexter12:显式创建一个完整类型然后使用它比使用迭代器块编写方法更优雅吗?我建议你尝试两种方式都写一个完整的解决方案,然后再判断......
  • @Jon:如果你看看他在问什么,不要尝试添加任何东西,是的,迭代器是最小、最快的解决方案。不过,根据他的要求,这似乎是遍历对象列表并查看当前项目与 IEnumerator 的用途完全一致,因此提出了建议。
  • @poindexter12:如果你创建了一个迭代器块,你仍然可以使用生成的IEnumerator&lt;DataObject&gt;。无论哪种方式,调用代码都是相同的 - 迭代器块只会为您节省很多麻烦。正如我所说,你应该尝试两种方式实现它......
【解决方案4】:

我认为您正在寻找的是:

List<DataObject> dataObjects = new List<DataObject>();

DataObject nextObject;
while((nextObject = ReadNextFile()).Category == "category")
{
   dataObjects.Add(nextObject);
}

但我不会那样做。我会写:

List<DataObject> dataObject = source.ReadItems()
                                    .TakeWhile(x => x.Category == "Category")
                                    .ToList();

其中ReadItems() 是一个返回IEnumerable&lt;DataObject&gt; 的方法,一次读取并产生一个项目。您可能希望使用迭代器块(yield return 等)来实现它。

这是假设您真的想在找到第一个具有不同类别的对象后立即停止阅读。如果你真的想包含 all 匹配的DataObjects, 将上述 LINQ 查询中的 TakeWhile 更改为 Where

(编辑:Saeed 已经删除了他对答案的反对意见,但我想我不妨把这个例子搁置一旁......)

编辑:证明这会奏效,因为 Saeed 似乎不相信我:

using System;
using System.Collections.Generic;

public class DataObject
{
    public string Category { get; set; }
    public int Id { get; set; }
}

class Test
{

    static int count = 0;

    static DataObject ReadNextFile()
    {
        count++;
        return new DataObject
        {
            Category = count <= 5 ? "yes" : "no",
            Id = count
        };
    }

    static void Main()
    {
        List<DataObject> dataObjects = new List<DataObject>();

        DataObject nextObject;
        while((nextObject = ReadNextFile()).Category == "yes")
        {
            dataObjects.Add(nextObject);
        }

        foreach (DataObject x in dataObjects)
        {
            Console.WriteLine("{0}: {1}", x.Id, x.Category);
        }
    }
}

输出:

1: yes
2: yes
3: yes
4: yes
5: yes

换句话说,该列表保留了对从 ReadNextFile 返回的 5 个不同对象的引用。

【讨论】:

  • 嘿,这似乎真的是最好的解决方案。然而有一件事,我正在读取的文件可能非常大(几兆字节)。预先阅读所有项目,这可能是性能问题吗?