List
is implemented in C# exactly as Stack
, see:
- https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.stack-1.push?view=netframework-4.8#remarks
- https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=netframework-4.8#remarks
Given that:
Why should developers choose, List or Stack, while in fact they can have "2 in 1".
(Implementing Pop
for List
, would make it 2-in-1! ).
Coming from more flexible languages, I don't want to commit to a limiting-collection-type. Starting with Stack, maybe later as problem changes, I'll need to access an [index]
, alternatively w/Lists, maybe later need Pop
or List1.Add (List2.Pop())
often.
Why doesn't
RemoveAt()
return a value? What damage would happen if it returned a value (as actually done in JavaArrayList
)?Why no
Pop()
?
Looking at Python, Perl, Ruby and 'e', one does wonder how come a C# List
doesn't have Pop()
. I want the "Swiss army knife" of arrays. i.e. List
! It's convenient to use just one language construct, and avoid conversions, etc. As List
is implemented using array and supports Add
in O(1) (Add
is just a different name for Push
), why wasn't Pop
added as well?
Why do we need a C#
Stack
whenList
could easily do allStack
does, and more?Easy way to
pop
'd make me always use List, hence saving the need of conversions/casting betweenList
andStack
.
Am i the only C# dude, that wants a (slightly) more powerful List ? ( instead of having 2 weaker concepts and having to convert between them ). ( other than that c# is really good).
Update : Wow !! Thanks to this thread Microsoft had decided to add Pop to C# list !! https://github.com/dotnet/runtime/issues/31828. many thanks to the community, especially to user @Theraot.
4 Answers 4
Part of the point of objects is to limit the things you can do with them as well as enable you to do things.
If I could add items randomly into a stack then it wouldnt be a stack.
If I pop from a list should it return the first or last item added?
Limiting behaviour and having specialised objects is a good thing because it allows you to have sensible expectations about what the object will do without knowing the detail of the implementation.
-
Your comment on stack, makes sense.Thanks. However regarding List, there is already RemoveAt, that lets you remove from anywhere, but unlike jave, it does not return the removed.jsonphy– jsonphy2020年01月05日 22:40:52 +00:00Commented Jan 5, 2020 at 22:40
-
Well Python (and others) , has pop and pop0, for list. Systemverilog has pop_front and pop_back for their 'list'.jsonphy– jsonphy2020年01月05日 22:43:48 +00:00Commented Jan 5, 2020 at 22:43
-
Regarding 'Pop location is unclear', well you could say that for 'Add' too! ( add is super helpful)jsonphy– jsonphy2020年01月06日 08:51:55 +00:00Commented Jan 6, 2020 at 8:51
-
@rankeren I think you are missing the point. Add, Pop and RemoveAt don't have universal meaning. They are given meaning by the class they are in. If you add everything to all classes you just en up with a messEwan– Ewan2020年01月06日 09:17:32 +00:00Commented Jan 6, 2020 at 9:17
-
Pop
andAdd
do have a universal meaning! Show me one language wherePop
deletes items not from the end (by default)? show me one language thatAdd
adds items not in the end (by default)? Of course you can invent a language whereif (false)
d behave as
if(true)` does, at any other language, yet, that `d be a bad design.jsonphy– jsonphy2020年02月05日 20:46:22 +00:00Commented Feb 5, 2020 at 20:46
instead of having 2 weaker concepts and having to convert between them
Well, that is actually not how I see it. Since a List
allows one effectively to execute all stack-like operations, if one really needs "both concepts in one", using a List
allows this, so this is IMHO not a "weaker" concept.
Of course, the equivalent of a Pop()
operation on a List requires a little bit more code, but if that really bothers you, because in your specific context you need that operation frequently, you can write an extension method for it, like this:
static class ListExtensions
{
public static T Pop<T>(this List<T> list)
{
int lastIdx = list.Count - 1;
T lastItem = list[lastIdx];
list.RemoveAt(lastIdx);
return lastItem;
}
}
(Similarly, you could implement something like a PopAt
operation, which means a RemoveAt
with returning the removed value.)
I understand your question in this sense: why did the .Net framework designers not provide such a method directly as part of the List<T>
class? For getting a binding answer, you need to ask those library designers. But I can imagine the follwing potential reasons:
Mixing up two concepts in one class does not make the class more readable and understandable. Historically, arrays, lists and stacks are usually taught as different concepts in most CS courses, and the terminology which includes Push, Peek, and Pop is exclusively used the context of stacks. So having one class for each of these concepts fits better to this than mixing up two of those concepts in one class, even it would technically be possible
Any additional feature in a class requires additional effort to specify it, implement it, test it, review it, document it, release it and maintain it. So the designers are usually very reluctant to add features which can be easily added by the users of the classes by themselves.
The number of cases where one really needs a Pop operation on a random access list is probably small enough for justifying to let the users implement their own function in case they really require it.
-
Thanks for your answer! Note that all other languages I know, do have pop in there 'list'. (Python, perl, ruby, e, systemverilog, and I bet many more). Except java and c#. C# wouldn't even return value for RemoveAt. Go figure.jsonphy– jsonphy2020年01月05日 22:52:56 +00:00Commented Jan 5, 2020 at 22:52
-
'Add' is super common, 'Pop' is sort of undo for it, and I find it very helpful. ( so do so many modern languages)jsonphy– jsonphy2020年01月05日 23:08:20 +00:00Commented Jan 5, 2020 at 23:08
If you want a "slightly more powerful list", then implement it. Make use of extension methods to add what you want to that class. For example, the following code will give you a list that returns the value when removing an element and provides Push
and Pop
:
using System;
using System.Collections.Generic;
public static class ListExtensions
{
public static T FetchAndRemoveAt<T>(this List<T> list, int index)
{
if (index >= list.Count) throw new ArgumentOutOfRangeException(nameof(index));
var value = list[index];
list.RemoveAt(index);
return value;
}
public static void Push<T>(this List<T> list, T value) => list.Add(value);
public static T Pop<T>(this List<T> list) => list.FetchAndRemoveAt(list.Count-1);
}
It's a naïve implementation that's certainly not thread-safe, but it may meet your needs. If not, write it the way you need it to be. If you think others would find it useful, publish it as a nuget package. So rather than worrying why the designers didn't implement things the way you want, you can implement them that way very easily.
-
Guess, I got it wrong, the real question is why Java made its removeAt return a value, while they could allow users to sweat on that detail. We should also ask Python to remove pop from their list, as that is also something users need to sweat on. I tend to believe those langs spoil their users way too much.jsonphy– jsonphy2020年01月07日 14:28:47 +00:00Commented Jan 7, 2020 at 14:28
-
"the real question is why Java made its removeAt return a value". Indeed, that is a good question to ask. In over 15 years of using C#, you are literally the first person I've encountered that wants
RemoveAt
to return the value. I suspect that almost no Java developer has ever used that return value therefore. And Python is a dynamic language. The "Swiss Army Knife" approach of overloading a small number of types with multiple purposes makes sense for dynamic languages.David Arno– David Arno2020年01月07日 15:44:39 +00:00Commented Jan 7, 2020 at 15:44 -
RemoveAt (0) is just pop0(aka dequeue). List as a fifo model, is very common in some areas, ( despite O(n)) . RemoveAt (count-1), is pop, very common, and also the 'undo' of Add. I hardly see what dynsmic typing has to do here. Systemverilog and e are strong typed, but Support pop. Would adding pop 'd make c# dynamic?jsonphy– jsonphy2020年01月07日 16:19:46 +00:00Commented Jan 7, 2020 at 16:19
Why should developers choose, List or Stack, while in fact they can have "2 in 1". (Implementing Pop for List, would make it 2-in-1! ).
This could mean bad encapsulation. You pass your Listack to third party code, and it uses in ways that you do not expect, resulting in an invalid state for your code. There is value in limiting what objects can do. It is good for defensive programming. See also interface segregation.
Coming from more flexible languages, I don't want to commit to a limiting-collection-type. Starting with Stack, maybe later as problem changes, I'll need to access an [index], alternatively w/Lists, maybe later need Pop or List1.Add (List2.Pop()) often.
Right. You may decide in the future that the type or interface you are using is not adequate and want to change it. And of course we do not want this change to propagate.
Well, if your methods receive objects by an interface, you can change the type without changing where you use it. Then, when you need to change to a type that has the same capabilities (and thus the same interfaces) plus some new ones... well, all methods that used the object before should still work, given that they use an interface that you are not removing... you are adding. So the change does not propagate out of control. See also interface segregation.
Why doesn't RemoveAt() return a value?
As Raymond Chen would put it:
features start out nonexistent and somebody has to make them happen.
-- source
There is value in asking why the other languages do it, and whatever or not those reasoning apply to .NET. Oh, yes, we are talking about the standard library, not the language C#. If that reasoning applies... well...
What damage would happen if it returned a value (as actually done in Java ArrayList)?
At source level, there should be no problem. Nobody is using the return value of RemoveAt
, given that it hasn’t one, and thus, no source code would be broken.
However, IL is a stack language. And the return value would be placed in the stack, and had to be discarded with a pop
. Thus, this change would break assemblies. We are talking of recompiling them. Existing assemblies could continue to work in an old runtime.
That of course, does not explain why they didn't do it in the first place.
With that said, a new method could be added to List<T>
that does what you want...
Why no Pop()?
Right. Like adding a List<T>.Pop
method. Adding that should be possible.
You could try posting an issue to dotnet/runtime, maybe a merge request.
If they don't want to do it, they will tell you why.
And perhaps in the next release...
Why wait? You could implement Pop
for List<T>
as an extension method. I like David Arno's solution.
Why do we need a C# Stack when
List
could easily do allStack
does, and more?
You don't. And you do not need List
either, you can work with arrays. Stack
exists for convenience of its use cases, and so does List
. I can only guess that the uses cases you have in mind where not considered for List
.
However, we may want the distinction. The more limited is an object, the more certain we are about how it will be used. And thus, we may want a limited object to ensure it is not misused/abused.
Easy way to pop 'd make me always use List, hence saving the need of conversions/casting between List and Stack
I would consider making an adapter for List
that wraps it and works like a Stack
. You may even provide IProducerConsumerCollection<T>
.
Am i the only C# dude, that wants a (slightly) more powerful List ?
No. Some people want, and have created other kinds of collections. There are Nugets out there... Although you are the first I have seen to want this particular change.
I don't think "List with Pop" are good search terms, I didn’t find a library for .NET that have that.
Perhaps what you want already exists. Perhaps you can create it and publish it for every body to use. Remember that features start out nonexistent and somebody has to make them happen
.
-
Fantastic answer! yet .. "This could mean bad encapsulation...The more limited is an object, the more certain we are about" . Correct ! but not relevant for list ... If there was no
removeAt
that 'd apply to list. GivenRemoveAt
,Pop
doen't makes list less encapsulated, nor less limited . (Pop
is merely a convenientremoveAt
+_index access_, at a common scenario). Adding Pop to list would not make it less encapsulated not less limited .jsonphy– jsonphy2020年02月05日 21:11:39 +00:00Commented Feb 5, 2020 at 21:11 -
@rankeren no, but giving
Insert
,RemoveAt
, etc... to aStack
does. So, should the method use the object as a stack?Theraot– Theraot2020年02月05日 21:16:15 +00:00Commented Feb 5, 2020 at 21:16 -
1@rankeren and solution has been proposed for your list: you can wrap it, you can add an extension method, you write your own, you can propose it to the dotnet team.Theraot– Theraot2020年02月05日 21:18:40 +00:00Commented Feb 5, 2020 at 21:18
-
1@rankeren True. Stack is well encapsulated, the code that uses it and passes it around might not. We have alternatives if we want better guarantees. Such as
ImmutableStack<T>
orConcurrentStack<T>
. Or perhaps we could pass aFunc<T>
for code that should only pop... evenIEnumerable<T>
. By the way, that reminds me, I do not think defensive programming is the right approach for every domain. In particular, if I'm writing both sides, it is not concurrent code, and it is not a public API, I'd be ok with relaxed guarantees.Stack
still has on its favor showing intent and deters bad use.Theraot– Theraot2020年02月05日 21:58:42 +00:00Commented Feb 5, 2020 at 21:58 -
1@rankeren I think you misread that. It was tagged to the Future milestone on Jun 24 of this year. You can check .NET 5.0 api for List<T> it does not contain Pop. This will probably be added for the next version afterwards. Looking at the roadmap... Wait until .NET 6.0 on November 2021, which is a LTS (and that is good). A long wait for a small feature in my opinion. I'd still suggest the extension method in the meantime.Theraot– Theraot2020年10月08日 15:06:44 +00:00Commented Oct 8, 2020 at 15:06
Pop()
because it is not a queue. There is aQueue
implementation or aStack
. Thing is, collection those types of collections have specific purposes and the implementations are optimized for that purpose. Just becauseList
can do it all, doesn't mean it does it all efficiently.