使用 .First() 和 .Count() 会破坏 IEnumerable 吗?

3
这是我的代码:
protected IEnumerable<Hotel> Hotels;
protected Hotel Hotel;

Hotels = from Hotel hotel in new Hotels()
         select hotel;

Response.Write("RES " + Hotels.Count() + "<br />");
if (Hotels.Count() > 0)
{
    Hotel = Hotels.First();
}
Response.Write("RES " + Hotels.Count() + "<br />");
Response.Write("RES " + Hotels.Count() + "<br />");

好的,酒店上有1个项目。但结果是:

RES 1
RES 0
RES 1

为什么?似乎.First()会与迭代器产生混淆?我如何使用IEnumberable修复它?(不使用其他类型的列表,我需要IEnumberable)。

注:IEnumberable是一个接口,定义了一种方法来支持在非泛型集合上进行简单迭代。

6
酒店信息是如何存储的?底层存储采用了什么方式,比如是否参考了一个酒店列表(List<Hotel>)? - undefined
1
可能是关于酒店实际实现的问题。例如,它在列表上运行正常。 - undefined
1
什么类实现了IEnumerable接口?如何初始化那个Hotels变量?它可以与列表、数组等一起正常工作。顺便说一句,你可以使用FirstOrDefault()扩展方法来代替那个if条件语句。 - undefined
2
刚刚在控制台应用程序中使用了一个列表进行测试,结果如预期的返回了1、1、1。 - undefined
1
我知道它适用于列表(List),但对于可枚举(Enumerable)却不行。这就是我的问题... - undefined
显示剩余5条评论
4个回答

3
static void Main(string[] args)
{
    IEnumerable<String> Hotels = new List<String>{"sdsfsdf"};
    String Hotel;

    Console.WriteLine("RES " + Hotels.Count());
    if (Hotels.Count() > 0)
    {
        Hotel = Hotels.First();
    }
    Console.WriteLine("RES " + Hotels.Count());
    Console.WriteLine("RES " + Hotels.Count());
}

打印

RES 1
RES 1
RES 1

正如预期的那样,你是如何填充可枚举的,并将其创建为什么类型?

如果您通过未执行的可查询方式来填充它,可能会出现奇怪的行为,例如它会选择第一个用法(在这种情况下是 First() )。


你能发布你的linq语句吗?这是直接来自数据库吗? - undefined
在你的 LINQ 查询结尾加上 .ToArray()。这会强制查询在 .First() 之前将结果填充到内存中,这意味着它将对 .NET 对象执行。 - undefined
嗯...添加.ToArray()有效!但是,为什么会这样呢?你能给我解释一下吗? - undefined
1
@markzzz:ToArray()执行一个底层的LINQ查询并返回一个集合本身,所以Count()将始终引用相同的项目列表。 - undefined
是的,发生的情况是你正在执行每个顺序语句而不是一个IQueriable。查询提供程序会决定如何将其转换为SQL,我想当你调用first时,它会将实体作为查询而不是可枚举对象获取,并因此清除基本集合。 - undefined
显示剩余4条评论

3

这里没有什么魔法。看起来Hotels集合中的项目数量在不断变化,可能是一些具有延迟执行的LINQ查询,甚至是LINQ-to-SQL。

请展示一个完整的填充Hotels的代码。


1

“First”方法可能会将迭代器推进到指向第二个元素。您对“Count”的第一次调用然后完成了对一个元素序列的迭代,随后对count的调用重新开始迭代。Count将始终迭代整个序列,因此它是幂等的。您需要一种方法来重置枚举器Hotels.Reset()First()之后使Count()正确运行。

最简单的方法可能是自己制作一个Reset扩展,但我无法复现您的问题。如果您有一些系统生成的数据库迭代器,则可能不会自动重置,您可以使用下面的代码。它只利用了您自己的发现,即单个Count()会重置迭代器。并且它将是O(n)完成。

static class Extensions
{
    public static void Reset<T>(this IEnumerable<T> toReset )
    {
        if (toReset != null)
        {
            int i = toReset.Count();
        }
    }
}

但我无法重现您的问题:

下面的代码始终给出正确的结果。您是否编写了自己的枚举器或集合?

static void Main(string[] args)
{
    var Response = System.Console.Out;

    var Hotels = new[]{1, 2, 3, 4};
    var Hotel = 0;

    Response.Write("RES " + Hotels.Count() + "<br />");
    if (Hotels.Count() > 0)
    {
        Hotel = Hotels.First();
    }
    Response.Write("RES " + Hotels.Count() + "<br />");
    Response.Write("RES " + Hotels.Count() + "<br />");

    Console.WriteLine( "Hotel: " + Hotel);
}

IEnumerable上没有.Reset()方法,已经检查过了 :( - undefined
2
不,它在迭代器上。你在使用哪个集合来表示“Hotels”? - 我无法使用数组重现你的错误。 - undefined
markzzz,我猜你使用了一些奇怪的对象作为你的IEnumerable<Hotel>。可能是你自己实现的东西搞乱了。 - undefined
然后我认为底层数据源会随着时间的推移而发生变化,因此会得到不同的结果... - undefined

1
根据底层实现的不同,重新枚举一个IEnumerable不能保证以任何方式返回相同的值。为了理解原因,请考虑以下示例;
static void Main(string[] args)
{
    IEnumerable<int> bop = RandomSequence();
    Console.WriteLine(bop.Count());
    Console.WriteLine(bop.Count());
}

private static int _seed = 0;
static IEnumerable<int> RandomSequence()
{
    var random = new Random(_seed++);
    int randomNumber;
    while ((randomNumber = random.Next(100)) != 0)
        yield return randomNumber;
} 

这是一个完全有效的IEnumerable,而两个对Count()的调用将计算出两个不同的伪随机值。原因是Count()重新枚举相同的IEnumerable并生成完全不同的随机序列。

如果您想要可重复的枚举,您需要在可枚举对象上调用ToList()ToArray()以存储结果,并从List/数组中进行所有枚举,该枚举每次都以相同的方式枚举。


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