tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

Best way to create an empty collection (array and list) in C# (.NET)

4 Aug 2020 4 mins .NET, .NET Core, C#

I one of APIs I was working a while back I needed to return an empty collection. It was not a performance critical code, yet I decided what would be the best way to do it. Exploring and learning.

The signature required me to return List<T>, but I started with testing arrays first. I expected Array.Empty to be clear winner, but I was also interested how other “common” ways stack up.

Array

I used new TestArray[0] (Ctor), new TestArray[] { } (CtorInit), Array.Empty<TestArray>() (ArrayEmpty) and Enumerable.Empty<TestArray>().ToArray() (EnumerableEmpty).

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Ctor 3.3639 ns 0.0647 ns 0.0605 ns 1.000 0.00 0.0076 - - 24 B
CtorInit 3.0780 ns 0.0634 ns 0.0593 ns 0.915 0.03 0.0077 - - 24 B
ArrayEmpty 0.0000 ns 0.0000 ns 0.0000 ns 0.000 0.00 - - - -
EnumerableEmpty 8.2498 ns 0.0616 ns 0.0546 ns 2.450 0.05 - - - -

As expected, the Array.Empty is clear winner. That makes sense, because it just returns a reference to a static generic class with a static field holding the empty array.

The Ctor and CtorInit are virually the same, because it’s just a different syntax for same IL. The downside is obviously the allocation of the real array, there’s no caching, etc.

The Enumerable.Empty version is slowest, kind of expected. But surprisingly it does not allocate. The reason is the implementation behind it is EmptyPartition<TElement> with specific ToArray implementation where again Array.Empty is used. By the way, the file where EmptyPartition<TElement> is, is called Partition.SpeedOpt.cs.

OK, that was plain old array. But how about the list (that I actually needed)?

List

The list is different because there’s no List.Empty or something like that. In the similar fashion I used new List<TestList>() (Ctor), new List<TestList>(0) (Ctor0), Array.Empty<TestList>().ToList() (ArrayEmpty) and Enumerable.Empty<TestList>().ToList() (EnumerableEmpty).

The first two look the same, but Ctor0 has a a little bit more code to execute (including branching).

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Ctor 5.973 ns 0.1152 ns 0.1077 ns 1.00 0.00 0.0102 - - 32 B
Ctor0 12.417 ns 0.3100 ns 0.3317 ns 2.09 0.08 0.0102 - - 32 B
ArrayEmpty 39.949 ns 0.1956 ns 0.1527 ns 6.71 0.12 0.0101 - - 32 B
EnumerableEmpty 16.719 ns 0.4013 ns 0.6366 ns 2.87 0.12 0.0102 - - 32 B

So, no matter the approach all allocate and allocate the same amount (the list itself, which in turn contains the array and 2 int fields).

Surprisingly, the Ctor compared to Ctor0 is about a half. I wasn’t expecting that much of a difference. Learning new stuff…

The EnumerableEmpty is next and again relying on the EmptyPartition<TElement>’s specific ToList implementation (which in turn just does the same call as Ctor).

Finally, the ArrayEmpty is slowest because there’s no specific optimization for this case.

Summary

The fastest way to get an empty array in using Array.Empty<T>() call. For list, it is just using new List<T>() aka the most straightforward way. No crazy ideas.

Appendix

Execution was done in this environment.

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.959 (1909/November2018Update/19H2)
Intel Core i5-7500 CPU 3.40GHz (Kaby Lake), 1 CPU, 4 logical and 4 physical cores
.NET Core SDK=3.1.302
  [Host]     : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
  DefaultJob : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT

Profile Picture Jiří Činčura is an independent developer focusing on data and business layers, language constructs, parallelism and databases. Specifically Entity Framework, asynchronous and parallel programming, cloud and Azure. He's Microsoft Most Valuable Professional and you can read his articles, guides, tips and tricks at www.tabsoverspaces.com.