【问题标题】:How does linq Last() determine the last item?linq Last() 如何确定最后一项?
【发布时间】:2010-05-20 10:14:28
【问题描述】:

我不明白 Current 如何可以为 null 并且 LINQ 函数 Last() 可以返回一个对象。我认为 Last 使用 GetEnumerator 并一直持续到 current == null 并返回对象。但是,正如您所看到的,第一个 GetEnumerator().Current 为 null,最后一个以某种方式返回一个对象。

linq Last() 是如何工作的?

items.GetEnumerator().Current
items.Last()

【问题讨论】:

  • 您将序列与其枚举数混淆了。想象一本带有编号页的书。那是一个序列。想象一个书签,标记一个特定的页面。那是一个枚举器。如果需要,您可以在一本书中拥有一百个书签,所有书签都标记不同的位置。调用 GetEnumerator.Current 是在您尚未将书签放入书中时询问书签所在的页面;不要那样做。

标签: c# .net linq


【解决方案1】:

System.Core.dll 上使用Reflector

public static TSource Last<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    IList<TSource> list = source as IList<TSource>;
    if (list != null)
    {
        int count = list.Count;
        if (count > 0)
        {
            return list[count - 1];
        }
    }
    else
    {
        using (IEnumerator<TSource> enumerator = source.GetEnumerator())
        {
            if (enumerator.MoveNext())
            {
                TSource current;
                do
                {
                    current = enumerator.Current;
                }
                while (enumerator.MoveNext());
                return current;
            }
        }
    }
    throw Error.NoElements();
}

【讨论】:

    【解决方案2】:

    Last() 将调用GetEnumerator(),然后继续调用MoveNext() / Current 直到MoveNext() 返回false,此时它返回检索到的Current 的最后一个值。 Nullity 通常不用作序列中的终止符。

    所以实现可能是这样的:

    public static T Last<T>(this IEnumerable<T> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }
        using (IEnumerator<T> iterator = source.GetEnumerator())
        {
            if (!iterator.MoveNext())
            {
                throw new InvalidOperationException("Empty sequence");
            }
            T value = iterator.Current;
            while (iterator.MoveNext())
            {
                value = iterator.Current;
            }
            return value;
        }
    }
    

    (这可以通过foreach循环实现,但上面更明确地显示了交互。这也忽略了直接访问最后一个元素的可能性。)

    【讨论】:

    • 另外,collection.GetEnumerator().Current 不是返回“集合的第一个元素之前的对象”吗?在 Current 具有定义值之前,您是否需要至少调用一次 MoveNext 的合同的一部分?
    • @Lasse:是的,尽管 IIRC 由 C# 生成的迭代器块忽略了这一点:(
    • MoveNext() Current之前调用,在if块中。
    【解决方案3】:

    枚举数的第一个值,在任何 MoveNext() 之前,在数组的情况下,是索引 -1 处的项目。
    您必须执行一次 MoveNext 才能进入实际收藏。
    这样做是为了使枚举器的构造函数不做太多工作,并且这些构造是有效的:

    while (enumerator.MoveNext()) {
          // Do Stuff
     }
    
    if(enumerator.MoveNext()) {
          // Do Stuff
     } // This is identical to Linq's .Any(), essentially.
    

    【讨论】:

      【解决方案4】:

      请记住,调用GetEnumerator 通常/不一定每次都返回相同的Enumerator。此外,由于thing.GetEnumerator() 返回一个新的Enumerator,它将开始未初始化(您尚未调用MoveNext()),thing.GetEnumerator().Current 根据定义始终为空。

      (我认为...)

      【讨论】:

      • 结果未定义,虽然通常为 null,但仍然未定义。
      【解决方案5】:

      如果您在备注部分查看IEnumerator Interface,他们将说明以下内容:

      最初,枚举器位于集合中的第一个元素之前在这个位置,Current 是未定义的。因此,在读取 Current 的值之前,您必须调用 MoveNext 将枚举数前进到集合的第一个元素。

      所以您必须拨打MoveNext() 一次才能获得第一项。否则你将一无所获。

      【讨论】:

      • 这解释了我的思维过程出了什么问题。谢谢!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-20
      • 1970-01-01
      • 1970-01-01
      • 2015-11-28
      相关资源
      最近更新 更多