.net System.Linq.Enumerable.Reverse是否将所有元素内部复制到数组中?

.net System.Linq.Enumerable.Reverse是否将所有元素内部复制到数组中?,.net,linq,.net-4.0,.net,Linq,.net 4.0,几年前。这是在2008年,所以问题是,框架4是否有一个优化的Linq.Reverse()实现,当集合类型允许时(例如IList),该实现不会具体化集合(即,将所有元素复制到内部数组)?编辑:是的,似乎已经进行了此更改 您链接到的bug报告将bug标记为已修复,但我想自己确认一下。所以,我写了这个小程序: static void Main(string[] args) { List<int> bigList = Enumerable.Range(0, 100000000).To

几年前。这是在2008年,所以问题是,框架4是否有一个优化的
Linq.Reverse()
实现,当集合类型允许时(例如
IList
),该实现不会具体化集合(即,将所有元素复制到内部数组)?

编辑:是的,似乎已经进行了此更改

您链接到的bug报告将bug标记为已修复,但我想自己确认一下。所以,我写了这个小程序:

static void Main(string[] args)
{
    List<int> bigList = Enumerable.Range(0, 100000000).ToList();

    Console.WriteLine("List allocated");
    Console.ReadKey();

    foreach (int n in bigList.Reverse<int>())
    {
        // This will never be true, but the loop ensures that we enumerate
        // through the return value of Reverse()
        if (n > 100000000)
            Console.WriteLine("{0}", n);
    }
}
现在让我们假设在这种情况下,
Enumerable.Reverse()
没有将输入
IEnumerable
复制到缓冲区。然后,循环

foreach (int n in Foo().Reverse())
    Console.WriteLine("{0}", n);

将遍历迭代器块以获取第一个
n
,遍历前999个元素以获取第二个
n
,依此类推。但这对
x
的影响与前向迭代不同,因为我们几乎每次迭代
x
时都会对
Foo()的返回值进行变异。为了防止正向和反向迭代之间的这种断开,
Enumerable.reverse()
方法必须复制输入
IEnumerable

显然不可能优化所有情况。如果某个对象只实现了
IEnumerable
,而没有实现
IList
,则必须迭代它直到最后一个元素。因此,优化只适用于实现
IList
(如
T[]
List
)的类型

现在,它是否在.NET4.5DP中进行了优化?让我们启动一个间谍:

公共静态IEnumerable反向(
这是(不可数的来源)
{
if(source==null)
{
抛出错误。ArgumentNull(“源”);
}
返回反向器(源);
}
好的,
ReverseIterator()
看起来怎么样

private static IEnumerable<TSource> ReverseIterator<TSource>(
    IEnumerable<TSource> source)
{
    Buffer<TSource> buffer = new Buffer<TSource>(source);
    for (int i = buffer.count - 1; i >= 0; i--)
    {
        yield return buffer.items[i];
    }
    yield break;
}
专用静态IEnumerable反转器(
IEnumerable(可数源)
{
缓冲区=新缓冲区(源);
对于(int i=buffer.count-1;i>=0;i--)
{
收益率返回缓冲区。项目[i];
}
屈服断裂;
}
迭代器块所做的是为集合创建一个
缓冲区
,并向后遍历该缓冲区。我们快到了,缓冲区是什么

[StructLayout(LayoutKind.Sequential)]
内部结构缓冲区
{
内部远程通讯[]项;
内部整数计数;
内部缓冲区(IEnumerable源)
{
TElement[]数组=null;
整数长度=0;
ICollection is2=源作为ICollection;
如果(is2!=null)
{
长度=是2。计数;
如果(长度>0)
{
数组=新远程通讯[长度];
is2.CopyTo(数组,0);
}
}
其他的
{
foreach(远程通讯本地源)
{
if(数组==null)
{
阵列=新远程通信[4];
}
else if(array.Length==长度)
{
远程通讯[]目的地阵列=新远程通讯[长度*2];
复制(数组,0,目标数组,0,长度);
数组=目标数组;
}
数组[长度]=本地;
长度++;
}
}
this.items=数组;
this.count=长度;
}
//又漏了一名成员
}
我们这里有什么?我们将内容复制到一个数组中。在任何情况下。唯一的优化是,如果我们知道
Count
(也就是说,集合实现了
ICollection
),我们就不必重新分配数组

因此,在.Net 4.5 DP中,
IList
的优化是而不是。在任何情况下,它都会创建整个集合的副本


如果我在阅读后猜测它为什么没有优化,我认为这是因为优化是可以观察到的。如果您在迭代时对集合进行变异,您将看到经过优化的更改数据,但没有优化的旧数据。由于向后兼容性,以微妙的方式实际改变某事物行为的优化是一件坏事。

而且实现是否针对.Net 4中的
IList
进行了优化?在“某些情况下”是不可能的:在没有
IList
@svick的情况下,我不知道该实现是否针对.NET 4中的
IList
进行了优化。也许可以问一个BCL开发人员?不幸的是,我自己不认识任何BCL开发人员。@AdamMihalcin,有一些简单的方法可以找到:实现
IList
,并观察
Reverse()
调用什么方法。还有一些复杂的方法(见我的答案)。观察任务管理器并不是衡量内存的好方法,特别是因为分配列表会产生大量垃圾。使用
GC.GetTotalMemory()
对我来说,在不强制收集的情况下,foreach前后的内存消耗为768MB/893MB,在强制收集时为512MB/893MB。提示:IlSpy函数类似于Reflector,我将把意识形态问题放在一边,但它能够将迭代器块反编译为具有
屈服中断的函数和<代码>收益率声明。我认为你和Jon是对的,这个优化没有实施,因为这是一个突破性的变化。有趣的是,OP提到的连接问题有Ed Maurer的评论,说他们确实计划实施它。太棒了!谢谢你挖掘斯维克@hvd,你说得对,它能做到。我已经更新了答案中的代码。谢谢,你要零钱吗?在我看来,这只适用于那些没有文档支持的假设的人,这些假设在任何情况下都会咬到你。比如说,如果
字典
曾经停止维护秩序(哪一个
public static IEnumerable<TSource> Reverse<TSource>(
    this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return ReverseIterator<TSource>(source);
}
private static IEnumerable<TSource> ReverseIterator<TSource>(
    IEnumerable<TSource> source)
{
    Buffer<TSource> buffer = new Buffer<TSource>(source);
    for (int i = buffer.count - 1; i >= 0; i--)
    {
        yield return buffer.items[i];
    }
    yield break;
}
[StructLayout(LayoutKind.Sequential)]
internal struct Buffer<TElement>
{
    internal TElement[] items;
    internal int count;
    internal Buffer(IEnumerable<TElement> source)
    {
        TElement[] array = null;
        int length = 0;
        ICollection<TElement> is2 = source as ICollection<TElement>;
        if (is2 != null)
        {
            length = is2.Count;
            if (length > 0)
            {
                array = new TElement[length];
                is2.CopyTo(array, 0);
            }
        }
        else
        {
            foreach (TElement local in source)
            {
                if (array == null)
                {
                    array = new TElement[4];
                }
                else if (array.Length == length)
                {
                    TElement[] destinationArray = new TElement[length * 2];
                    Array.Copy(array, 0, destinationArray, 0, length);
                    array = destinationArray;
                }
                array[length] = local;
                length++;
            }
        }
        this.items = array;
        this.count = length;
    }

    // one more member omitted
}