7
\$\begingroup\$

I often need to return empty collections.
One of those days I wrote the following to return a cached instance:

public static class Array<T>
{
    // As a static field, it gets created only once for every type.
    public static readonly T[] Empty = new T[0];
}

I didn't know about Enumerable<T>.Empty() maybe it didn't exist back then. Although I know now, I still use this one.

There are still many functions in BCL that need an array instead of IEnumerable<T>, IList<T> or IReadOnlyList<T>. And array implements all of these so it can be used anywhere.

// All these variables share the same array's reference.
string[]            empty1 = Array<string>.Empty;
IEnumerable         empty2 = Array<string>.Empty;
IEnumerable<string> empty3 = Array<string>.Empty;

What do you think about this class?
Can you see any other advantages/disadvantages of it over Enumerable<T>.Empty()?

And about implementation:
Do you think making the caching using a static field would cause any problem?


Edit: Array class now has a static, generic Empty method, essentially deprecating this implementation.

\$\endgroup\$
  • \$\begingroup\$ I wasn't aware of this little gem. Thanks! \$\endgroup\$ – Jesse C. Slicer Jan 11 '13 at 14:27
  • \$\begingroup\$ @Jesse: You sir, are very welcome. \$\endgroup\$ – Şafak Gür Jan 11 '13 at 14:46
  • \$\begingroup\$ What are your typical use cases for this class? \$\endgroup\$ – Leonid Jan 13 '13 at 15:16
  • \$\begingroup\$ @Leonid: I use it mostly on socket programming (to send an empty frame) and on collection returning methods that are called often: IEnumerable<Foo> GetRelatedFoos(Foo foo) { if (foo.Operations == 0) return Array<Foo>.Empty; return GetRelatedFoosInternal(foo); } where GetRelatedFoosInternal(Foo foo) is an iterator block. By separating these methods I can avoid initializing the iterator's state machine unless necessary and by returning Array<Foo>.Empty instead of new Foo[0] I can use the single, empty Foo[] instance for every GetRelatedFoos call that should return empty. \$\endgroup\$ – Şafak Gür Jan 15 '13 at 10:25
3
\$\begingroup\$

Your solution is absolutely correct and practical.

In fact Enumerable.Empty<T> also returns empty array under the hood, just slightly in a different way (they have a separate instance holder class that is lazily initialized).

\$\endgroup\$
1
\$\begingroup\$

While it most certainly is nifty to save a little bit of time by using cached collections, returning the same empty collection every time an empty collection needs to be returned is digging yourself into a hole:

Consider what happens if the user decides to also 'save a little bit of time' by re-using the collection that your function returned. The empty collection you were returning everywhere would not be empty anymore and the re-used empty collections would cause many bugs.

I would say best practice is to use a memorypool of any kind to pool the collections you use in time-critical code and using that to get the empty collection. This way you achieve the same speed as when returning a cached collection, but without the limitation that the empty collection cannot be used for anything, ever.

\$\endgroup\$
  • 2
    \$\begingroup\$ Arrays are non-resizable, so there is no risk with it becoming anything but 'empty'. The only way I can see this possibly going wrong is if you compare references, either expecting all empty arrays to be the same, or (for some bizarre reason) using such arrays as keys (both seem unlikely, and would probably indicate a deficiency elsewhere). I don't think there was any suggestion in the OP to provide such a facility for List or other such dynamic data structures. \$\endgroup\$ – VisualMelon Jul 29 '17 at 17:53
0
\$\begingroup\$

I think it entirely depends on context.

Enumerable<T>.Empty() was clearly introduced for syntactic sugar in Linq. I see no reason to use it over a plain old new-statement.

The wider question about caching and efficiency depends on the code-base. Calling a list "Empty" explicitly states intent and could help readability, but if you later go on to add items to that collection in any way, it actually makes the code more confusing.

As far as any problems with the "cached, static" field go, the obvious problem is that if someone was foolish enough to place a lock on that static list, it could cause all manner of issues in your application as it'd act as a point of coupling.

I'd always avoid using shared static references if you don't need them for any specific reason, especially if it's a perceived performance problem that has never been encountered. You can't really get much safer than an instance variable with a new empty list in it.

Conversely, if you're really up against the wire with performance, there may be a good reason here, but you'd probably find that if you're micro-optimising list initialisation, there's a better higher level optimisation to be made.

\$\endgroup\$
  • 2
    \$\begingroup\$ Well, one of the reasons I went with array is that arrays with no elements are completely immutable. Since the field is also read-only, there is no way someone can change that cached instance of the array (it will forever be empty). About making it static: There are a lot of public static fields (and singletons that return static instances) even in BCL, so the probability of someone may put o lock on them shouldn't affect an API's design in my opinion. Also, I believe if someone does that, he really deserves the problems it will cause. \$\endgroup\$ – Şafak Gür Jan 11 '13 at 14:14
  • \$\begingroup\$ +1 for your point on my approach being a micro-optimization though; you're right about that. I could just return new T[0] and that wouldn't hurt the performance in most of the cases but I couldn't see a reason for it. Now I know that Enumerable<T>.Empty() also does caching and I think locking Enumerable<string>.Empty() does probably the same thing with locking Array<string>.Empty. \$\endgroup\$ – Şafak Gür Jan 11 '13 at 14:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.