SafeList 2.0
The first version of SafeList
tried to address a common problem: inserting and removing elements into a List<T>
while you loop over it. It had a lot of problems though and ended up being pretty much useless. Today’s article presents SafeList
2.0, a radically-improved version that really solves the problem so you can actually use it as a drop-in replacement for List<T>
. Read on for the details, the source code, and even the unit tests that prove it handles all the nasty corner cases for you!
Let’s really briefly discuss the problem that SafeList
fixes for you. Any time you add or remove elements to a List<T>
while you’re looping over them, you have a problem. A foreach
loop will throw an exception on the next iteration. You could use a for
loop, but you still have to manually fix up your index (i
) variable. You also sacrifice the cleaner (and sometimes faster) foreach
syntax.
But if it were just these minor annoyances then SafeList
wouldn’t be of much use. It’s the complex cases that really make it worthwhile. Consider this innocent-looking function:
void Iterate(Action<int> callback) { for (var i = 0; i < list.Count; ++i) { callback(list[i]); } }
How do you know that callback
didn’t add or remove elements? How do you know that Iterate
wasn’t called by another function that was also looping over the same list? If anyone else was messing with the list, it’s super hard to keep everybody’s index variables synchronized.
SafeList
1.0 handled these cases by providing a class with an Iterate
function so it could keep track of loops over the list. It had functions like Add
and Remove
that queued up the added and removed elements until after all loops were done and then applied all the queued adds and then all the queued removes. This led to various problems including the list getting out of sync and added elements not being included in ongoing loops.
The second major problem was that SafeList
didn’t implement any of the interfaces that List<T>
does. This meant you couldn’t use it like an IEnumerable
and foreach
loop over it. You couldn’t add elements at construction time like this: new List<int>(){ 1, 2, 3 }
. It also didn’t implement hardly any of the many useful methods found on List<T>
.
All of these problems have been fixed for version 2.0.
The new-and-improved SafeList
implements all of the interfaces that List<T>
does: IList<T>
, IList
, ICollection<T>
, IEnumerable<T>
, IEnumerable
, and ICollection
. It also implements all of the extra functions that List<T>
provides. So you get the full functionality and adaptability of List<T>
in a safer package.
Internally, SafeList
now uses a for
loop and adjusts index variables as shown above. Because the methods of SafeList
are the only way to add or remove elements to the list, it can adjust the index variables of all known loops. It’s still possible to write a broken for
loop that indexes in to SafeList
, but any foreach
loop or loop using the ForEach
or GetEnumerator
methods is covered and doesn’t need to worry about changes to the list.
Also unlike the original SafeList
, version 2.0 comes with NUnit-based unit tests. These can be easily run using Unity Test Tools or any other NUnit runner since SafeList
does not depend on Unity.
As for usage, SafeList
is meant to be a drop-in replacement for List<T>
, so you can use it in exactly the same ways. Legally, usage should follow the MIT license. It should easily fit into almost any project.
Finally, I’ve uploaded both SafeList
and its unit tests to a new GitHub project. Feel free to fork and send me pull requests with any enhancements you’d like to add, or simple leave a comment instead.
I hope you find SafeList
useful in your projects. If you’ve got any questions about it or suggestions, please comment!
#1 by Ghat Smith on March 18th, 2021 ·
Just be careful, if you remove an existing value and add it back to the SafeList while iterating, you can have infinite loop. Can’t find of an easy and efficient fix for this…
P.S: I always get an internal server error when trying to add code blocks inside my comments