我在我的代码中使用了几个栈来跟踪我的逻辑位置。有时候,我需要复制这些栈,但似乎无法以保持顺序的方式克隆它们。我只需要进行浅层复制(引用,而不是对象)。
什么是正确的方法?还是应该使用其他类型的栈?
我看到了这篇文章Stack Clone Problem: .NET Bug or Expected Behaviour?,但不确定如何为Stack<T>
类设置克隆方法。
我在我的代码中使用了几个栈来跟踪我的逻辑位置。有时候,我需要复制这些栈,但似乎无法以保持顺序的方式克隆它们。我只需要进行浅层复制(引用,而不是对象)。
什么是正确的方法?还是应该使用其他类型的栈?
我看到了这篇文章Stack Clone Problem: .NET Bug or Expected Behaviour?,但不确定如何为Stack<T>
类设置克隆方法。
var clonedStack = new Stack<T>(new Stack<T>(oldStack));
您可以将此写成扩展方法,如下所示:
public static Stack<T> Clone<T>(this Stack<T> stack) {
Contract.Requires(stack != null);
return new Stack<T>(new Stack<T>(stack));
}
这是必要的,因为我们在这里使用的 Stack<T>
构造函数是 Stack<T>(IEnumerable<T> source)
。当您迭代 Stack<T>
的 IEnumerable<T>
实现时,它将重复从堆栈中弹出项目,因此反转了它们进入新堆栈所需的顺序。因此,执行两次此过程将使堆栈处于正确的顺序。var clonedStack = new Stack<T>(oldStack.Reverse());
public static Stack<T> Clone<T>(this Stack<T> stack) {
Contract.Requires(stack != null);
return new Stack<T>(stack.Reverse());
}
我们需要再次按照从堆栈迭代输出的反向顺序遍历堆栈。
我怀疑这两种方法之间没有性能差异。
对于那些关注性能的人来说,还有其他几种方法可以在不损失太多性能的情况下迭代原始堆栈成员:
public T[] Stack<T>.ToArray();
public void Stack<T>.CopyTo(T[] array, int arrayIndex);
我编写了一个初步的程序(链接将在本文末尾提供)来衡量性能,并为已经建议的实现(见Clone1和Clone2)添加了两个测试,以及ToArray和CopyTo方法的两个测试(见Clone3和Clone4,它们都使用更有效的Array.Reverse方法)。
public static class StackExtensions
{
public static Stack<T> Clone1<T>(this Stack<T> original)
{
return new Stack<T>(new Stack<T>(original));
}
public static Stack<T> Clone2<T>(this Stack<T> original)
{
return new Stack<T>(original.Reverse());
}
public static Stack<T> Clone3<T>(this Stack<T> original)
{
var arr = original.ToArray();
Array.Reverse(arr);
return new Stack<T>(arr);
}
public static Stack<T> Clone4<T>(this Stack<T> original)
{
var arr = new T[original.Count];
original.CopyTo(arr, 0);
Array.Reverse(arr);
return new Stack<T>(arr);
}
}
我们可以看到,使用CopyTo方法的方法比较快,同时实现也非常简单明了。此外,我对栈大小的最大值进行了快速研究:Clone3和Clone4测试在OutOfMemoryException发生之前适用于更大的堆栈大小:
由于显式/隐式定义的额外集合影响了内存消耗,因此上述Clone1和Clone2的结果较小。因此,Clone3和Clone4的方法允许更快地克隆堆栈实例,并减少内存分配。您甚至可以使用Reflection获得更好的结果,但这是另一回事 :)
完整的程序清单可以在此处找到。
Clone4
仍然是最有效的方法。这里有一种简单的方法,使用LINQ:
var clone = new Stack<ElementType>(originalStack.Reverse());
public static class StackExtensions
{
public static Stack<T> Clone<T>(this Stack<T> source)
{
return new Stack<T>(originalStack.Reverse());
}
}
使用方法:
var clone = originalStack.Clone();
Stack`1
,但只想快速复制现有的Stack`1
,以便您可以按照Pop
返回的顺序遍历它,另一个选项是将Stack`1
复制到Queue`1
中。元素将按照与原始Stack`1
相同的顺序Dequeue
,这些元素将按照它们从原始Stack`1
中Pop
的顺序Dequeue
。using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
var stack = new Stack<string>();
stack.Push("pushed first, pop last");
stack.Push("in the middle");
stack.Push("pushed last, pop first");
var quickCopy = new Queue<string>(stack);
Console.WriteLine("Stack:");
while (stack.Count > 0)
Console.WriteLine(" " + stack.Pop());
Console.WriteLine();
Console.WriteLine("Queue:");
while (quickCopy.Count > 0)
Console.WriteLine(" " + quickCopy.Dequeue());
}
}
输出:
栈: 最后压入,先弹出 中间位置 最先压入,最后弹出
队列: 最后压入,先弹出 中间位置 最先压入,最后弹出
Stack<T>
的IEnumerable<T>
实现是完全合理的;如果Stack<T>
的IEnumerable<T>
实现除了反复弹出和产生值以外做任何其他事情,我会感到困惑。对于Stack<T>(IEnumerable<T> source)
的构造函数也是完全合理的;如果这个Stack<T>
的构造函数除了从source
中取值并推入栈中之外做任何其他事情,我会感到困惑。在我看来,这其中没有什么奇怪的地方。 - jason