17

List<T> from System.Collections.Generic does everything Stack<T> does, and more -- they're based on the same underlying data structure. Under what conditions is it correct to choose Stack<T> instead?

jeuxjeux20
  • 391
  • 1
  • 6
  • 20
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • 9
    One could argue `Stack` conveys the intention more clearly when you want to use a stack. – CodesInChaos Sep 17 '12 at 20:57
  • 2
    They're two different data structures - `List` gives you a list of items, whereas `Stack` gives you a FILO (First In Last Out) queue – Dave Zych Sep 17 '12 at 20:58
  • 1
    @SimpleCoder No, but it can. There is no operation a stack can do that a list can't – Servy Sep 17 '12 at 20:58
  • 2
    @Servy I can't see Pop() and Push() in List, quite useful operations for a Stack. They can be emulated, but not necessarily efficiently. – Joachim Isaksson Sep 17 '12 at 21:01
  • 1
    @JoachimIsaksson In a List it would simply be adding to and remvoing from the end. Both of those operations are O(1) and supported in the existing API – Servy Sep 17 '12 at 21:03
  • 1
    @JoachimIsaksson They can be trivially emulated efficiently. `Push` is called `Add`. And `Pop` requires a bit of code, but is still O(1). `var result=list[list.Count-1];list.RemoveAt(list.Count-1);return result;` – CodesInChaos Sep 17 '12 at 21:05
  • @Servy Are you sure Add() is O(1)? I was under the impression (I may very well be wrong) that it was O(n) due to the need to reallocate/copy the complete List if there wasn't any free space left. – Joachim Isaksson Sep 17 '12 at 21:06
  • 1
    @JoachimIsaksson It's almost always O(1). It will, [ocasionally] be O(n). In either case, it will be the same for both List and Stack, as they are both backed by an array internally. – Servy Sep 17 '12 at 21:07
  • 2
    @JoachimIsaksson It's amortized constant time. – CodesInChaos Sep 17 '12 at 21:07
  • @JoachimIsaksson: `Stack.Push` may require reallocation of the entire underlying array as well. – Billy ONeal Sep 17 '12 at 21:09
  • @BillyONeal That is an implementation detail, a stack _could_ be O(1) while a List cannot (in any way I can see) without sacrificing lookup performance. In this case I see your point though, in .NET Stack is all the way as "bad" as List. – Joachim Isaksson Sep 17 '12 at 21:13
  • @JoachimIsaksson In comparing `List` and `Stack` (as in the .NET classes, not the computer science constructs) the performance of using stack-like operations on a `List` will be identical to a `Stack` from a performance perspective. From a more general CS perspective, the most efficient (from a practical performance perspective) implementation of a `Stack` happens to also be a suitable implementation for a `List` (meaning Microsoft didn't screw it up). – Servy Sep 17 '12 at 21:17
  • @Joachim: It isn't "bad" -- in most cases even with reallocations the amortized constant time implementations will beat the constant time implementations by huge margins. Storing all those pointers and doing all those allocations isn't cheap; particularly given that the number of reallocations for the amortized versions is bounded by lg(n), where n is the maximum size the container ever reaches. – Billy ONeal Sep 17 '12 at 21:18
  • 1
    Seriously, how on earth is this not constructive? – Billy ONeal Sep 17 '12 at 21:28
  • @BillyONeal `" this question will likely solicit debate, arguments, polling, or extended discussion"`. Take a look at the comments throughout this question/answer. Take a look at the number of answers and how much they have varied. Clearly it's a topic that solicits debate, arguments, and extended discussion. – Servy Sep 18 '12 at 13:50
  • 1
    @Servy: Tis frustrating that people commenting on a question or answers mark the *question* as something which should be closed. By that standard no design questions can ever be asked, because all design questions are going to have different ways of solving a given problem. :sigh: – Billy ONeal Sep 19 '12 at 03:12
  • @BillyONeal Which is why such questions (often) would be more appropriately asked on programmers.stackexchange.com – Servy Sep 19 '12 at 13:48
  • @Sevy: Then this should have been migrated there. Not closed. – Billy ONeal Sep 19 '12 at 17:55
  • Stack is an Abstract Data Type, it is possible that in the future its internal implementation may change to, say, use a linked list of array of items which is faster and more memory efficient when the size of the stack changes a lot. Client codes would not need to change because you know, for sure, that the data structure is never accessed with random access. You have less flexibility on that part with concrete data structure. – Lie Ryan Nov 04 '12 at 08:45
  • @Lie: Linked list is never faster or more efficient as a stack. As a queue, maybe, but not as a stack. Moreover, `System.Collections.Generic.Stack` is *not* an abstract data type. – Billy ONeal Nov 05 '12 at 18:03
  • @BillyONeal: Incorrect, a stack implemented using an array had to be resized from time to time and the cost of resizing is worse with arrays compared to if it was implemented using a linked list of blocks. – Lie Ryan Nov 05 '12 at 23:26
  • @Lie: Incorrect. The cost of the garbage collector having to walk each block on every collection is far more than the cost saved by avoiding resizes. The resize operation occurs geometrically, so it occurs at most O(lg(n)) number of times for n items in the stack, while the linked list implementation suffers a O(n) garbage collection penalty on every collection. – Billy ONeal Nov 05 '12 at 23:34
  • @BillyONeal: The block size of linked list of blocks does not have to increase linearly, and I think you probably should make measurements rather than making guesses. Also, your `n` in `O(lg(n))` of array is not the same `n` as in `O(n)` of linked list, such comparison simply makes no sensible conclusion. – Lie Ryan Nov 05 '12 at 23:44

6 Answers6

17

You would use stack if you had a need for a Last In First Out collection of items. A list will allow you to access it's items at any index. There are a lot of other differences but I would say this is the most fundamental.

Update after your comment:

I would say that using Stack<T> makes a statement about how you want this code to be used. It's always good to plan for the future, but if you have a need for Stack<T> right now, and no compelling reason to use List<T> then I would go with Stack<T>

Abe Miessler
  • 82,532
  • 99
  • 305
  • 486
  • I know what the difference is between a stack and a vector. I'm asking why I would artificially limit myself to using Stack in new code, given that some future code might want the additional bits List provides. (Given that, presumably, the implementations are the same underneath) – Billy ONeal Sep 17 '12 at 21:03
  • 1
    @BillyONeal If you wanted something not in the stack API then you aren't logically representing a stack and shouldn't have used it in the first place. – Servy Sep 17 '12 at 21:04
  • 4
    I would say that using `Stack` makes a statement about how you want this code to be used. It's always good to plan for the future, but if you have a need for `Stack` right now, and no compelling reason to use `List` then I would go with `Stack` – Abe Miessler Sep 17 '12 at 21:06
  • 1
    @Servy On the other hand, using a List in the first place lets you use all the Stack functionality with no significant performance hit, and adds flexibility in case you decide to refactor later. On balance I agree with you and think that if you decide you want a list and not a stack you should make that decision when it needs to be made and not right off the bat, but I can see both sides. – Jeremy Todd Sep 17 '12 at 21:07
  • 2
    @JeremyTodd And then what happens when the next developer comes along and doesn't know that your `List` list is logically a `Stack` and tries to add/remove/read somewhere other than the end? Additionally if you are logically representing a stack you won't need random access later, that's the point. Such situations aren't exactly rare; `Stack` is a useful logical data structure. – Servy Sep 17 '12 at 21:09
  • 2
    @JeremyTodd, It would make the code more complex though. A call to `Stack.Pop()` could be implemented for a `List` but it would not be nearly as clean. It would also look a little strange to someone coming across that code later. – Abe Miessler Sep 17 '12 at 21:11
  • @Servy Well I certainly wouldn't propose making it a list unless you intended to support full list functionality! But designing with flexibility in mind (i.e. deciding to implement as a list even though a stack would suffice for the immediate purposes) has a certain seductive appeal. Like I said though, on balance I agree with you. – Jeremy Todd Sep 17 '12 at 21:11
  • I'm thinking of things like "The code right now uses the collection as a stack. But someday in the future I add a ToString overload around my class (which does not expose a Stack or List externally; used only for implementing the class) which wants to print the current state of the stack, but it can't do that because the stack must be accessed in the right order. (either that or quite a lot of copying is required for that....) – Billy ONeal Sep 17 '12 at 21:15
  • @JeremyTodd: except that Stack misses a lot of operations which are common on stacks, like a one-shot remove of the topmost N elements, or random access *relative* to the stack top. – Marco Mp May 24 '14 at 17:03
6

why I would artificially limit myself to using Stack in new code

There's your answer - you should use Stack when you have a need to enforce a contractual expectation that the data structure being used can only be operated on as a stack. Of course, the times you really want to do that are limited, but it's an important tool when appropriate.

For example, supposed the data being worked with doesn't make any sense unless the stack order is enforced. In those cases, you'd be opening yourself up to trouble if you made the data available as a list. By using a Stack (or a Queue, or any other order-sensitive structure) you can specify in your code exactly how the data is supposed to be used.

Chris Walsh
  • 3,423
  • 2
  • 42
  • 62
dimo414
  • 47,227
  • 18
  • 148
  • 244
  • 2
    well `supposed the underlying data structure can't be efficiently operated on as a list` the entire point of the post is that that won't ever be the case. – Servy Sep 17 '12 at 21:11
  • What if the data structure's a wrapper for some sort of external resource? Like I said, it's not common, but if it didn't exist in our toolbelt, there'd be inverse questions asking "Why can't I create a contract in my code that a list should only be used as a stack?" – dimo414 Sep 17 '12 at 21:14
  • `"What if the data structure's a wrapper for some sort of external resource?"` If that's the case then you would have a class with an API similar to that of a stack. Note that `Stack` isn't an interface, it's a concrete implementation that can't be backed by some "external resource". `"Why can't I create a contract in my code that a list should only be used as a stack?"` I discuss that in my answer. They could have made `Stack` just an interface that `List` implemented, which would allow you to do that. You could make your own interface and an implementation of it that was backed by `List`. – Servy Sep 17 '12 at 21:20
  • No, you missed the point. `Queue` is very different than `List` and `Stack`. (I suspect Queue is either a deque implementation, or implements the amortized constant time queue structure with the padding around the actual queue) But List and Stack are the same data structure with different wrappers around them. These are concrete implementations, not hypothetical list and stack interfaces. – Billy ONeal Sep 17 '12 at 21:22
  • 1
    @BillyONeal `Queue` is implemented as a circular array (based on what's in the MSDN page anyway), not as a deque. It's rather unfortunate that .NET has no deque implementation in `System.Collection`. – Servy Sep 17 '12 at 21:24
  • There are few or no constructs in .NET implemented as true "linked lists", consisting of only Nodes that reference their content and their Next (or Previous) items. There's a very good reason; linked lists perform *terribly* on the memory heap. An array is a fixed contiguous block of memory; linked list items can be scattered all the way through the heap. – KeithS Sep 17 '12 at 21:33
  • 1
    @BillyONeal - In fact what Servy said is *exactly* what MSDN said. A Queue uses a contiguous array of fixed size, and when that array is full and another item must be added, the object copies the existing array into one twice as large. This array-based internal structure is common to most objects of System.Collections; the differences are in the available access (and in collections like HashSet/Dictionary, the number of layers of these arrays). – KeithS Sep 17 '12 at 21:39
  • @KeithS: Derp. That's correct. For some reason I read that as "Circular linked list" :sigh:. +1 to both. – Billy ONeal Sep 17 '12 at 21:57
  • @KeithS Well, there is of course the `LinkedList` that is a true linked list. Other than that, the only noteworthy construct in .NET that I know of that uses a LinkedList is the StringBuilder, which (in it's current implementation) used a hybrid of techniques, but is primarily based on what is essentially a LinkedList of strings. – Servy Sep 18 '12 at 13:53
5

Well, you would want to use Stack if you were logically trying to represent a stack. It will convey the intention of the programmer throughout the code if you use a stack, and it will prevent in-advertant mis-use of the data structure (unintentionally adding/removing/reading somewhere other than one end).

It's certainly possible that, rather than a concrete implementation, Stack could just be an interface. You could then have something like List implement that interface. The problem there is mostly a matter of convenience. If someone needs a stack they need to pick some specific implementation and remember ("Oh yeah, List is the preferred stack implementation") rather than just newing up the concrete type.

Abe Miessler
  • 82,532
  • 99
  • 305
  • 486
Servy
  • 202,030
  • 26
  • 332
  • 449
4

It's all about concept. A List is a List, and a Stack is a Stack, and they do two very different things. Their only commonality is their generic nature and their variable length.

A List is a variable-length collection of items in which any element can be accessed and overwritten by index, and to which items can be added and from which items can be removed at any such index.

A Stack is a variable-length collection of items supporting a LIFO access model; only the top element of the Stack can be accessed, and elements can be added to and removed from only that "endpoint" of the collection. The item 3 elements from the "top" can only be accessed by "popping" the two elements above it to expose it.

Use the correct tool for the job; use a List when you need "random" access to any element in the collection. Use a Stack when you want to enforce the more limited "top-only" access to elements in the array. Use a Queue when you want to enforce a FIFO "pipeline"; items go in one end, out the other.

KeithS
  • 70,210
  • 21
  • 112
  • 164
  • In this example, one only needs LIFO access today. But one might need non-LIFO access tomorrow. Consider a class which walks the filesystem, and stores directory names internally in a stack. A stack is fine because this code only needs LIFO access. But tomorrow one wants to add a ToString overload which prints the entire contents of the stack from beginning to end (e.g. to print the directory path). Now to add this overload all the code that touched the stack has to change. – Billy ONeal Sep 17 '12 at 21:26
  • 1
    Nope. Open-closed principle: derive from Stack, override ToString() and use Stack's Enumerable implementation to produce the needed string representation of all elements. Consuming code doesn't have to know it's your StringableStack and not .NET's Stack that's doing the ToStringing. – KeithS Sep 17 '12 at 22:13
0

Besides being conceptually different, as the other the answers already point out, there are also different methods in the Stack that make your code cleaner (and your life easier) than the correspondent code when using a List.

For example, a simple object pool snippet when using a Stack:

if (!pool.TryPop(out var obj))
{
    obj = new Foo();
}

And the same written as a List while keeping the operation O(1):

Foo obj;
int count = pool.Count;
if (count > 0)
{
    obj = pool[--count];
    pool.RemoveAt(count);
}
else
{
    obj = new Foo();
}
Jorge Galvão
  • 1,729
  • 1
  • 15
  • 28
-1

System.Collections.Generic.Stack<T> is a LIFO (Last-In, First-Out) data structure aka a stack.

Despite its name, SCG.List<T> is not the abstract data type known as a [linked] list: it is, in fact, a variable-length array.

Two very different creatures.

Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • 3
    System.Collections.Generic.Stack is a vector, just as System.Collections.Generic.List is. (That is my whole point in this question) – Billy ONeal Sep 17 '12 at 21:05