Empty Array Enumerators

Speaking of Array.Empty<T>(), there’s a related change in .NET Framework 4.6 and .NET Core: empty arrays now return a single cached instance of IEnumerator<T> when GetEnumerator() is called.

Previously, calls to GetEnumerator() on an empty array would always return a new enumerator instance. That’s no longer the case!

Array.Empty<T>()

I occasionally find myself in situations where it is desirable to use or return an empty array or collection. If the empty case is common, I’ll usually cache and reuse the empty array as an optimization to avoid unnecessary future allocations, particularly when writing high performance library code.

For example:

private static byte[] s_empty;

public byte[] GenerateBytes()
{
    // Fast path for the empty case
    if (IsEmpty)
    {
        return s_empty ?? (s_empty = new byte[0]);
    }

    // Actual operation here...
}

This can lead to many private static s_empty fields sprinkled throughout a code base, with each field holding a reference to its own private empty array instance, ruling out any sharing that could further reduce allocations across a code base. Of course, a central cache of empty arrays can be used within a code base to enable sharing, but such coupling isn’t always desirable and it doesn’t enable sharing across all application code and libraries (including third party and .NET Framework libraries) used in an application.

.NET Framework 4.6 and .NET Core address this with the new Array.Empty<T>() method, which, as the name implies, returns an empty array. Array.Empty<T>() provides a central cache of empty array instances, enabling sharing of the empty instances across all libraries and application code that use it.

It’s currently implemented as follows:

public abstract class Array : ...
{
    ...

    public static T[] Empty<T>()
    {
        return EmptyArray<T>.Value;
    }

    ...
}

internal static class EmptyArray<T>
{
    public static readonly T[] Value = new T[0];
}

The first time Array.Empty<T>() is called for a given T, a new empty T[] array is created, cached, and returned; subsequent calls return the cached instance.

Note: While this is how Array.Empty<T>() is currently implemented, the documentation only states that it “returns an empty array” – there’s no mention of caching or returning the same cached instances. Presumably this is intentional to allow for potential future fine-tuning of the cache strategy (e.g. I could imagine future changes that allowed for unused instances to be garbage collected after some expiration threshold).

Regardless, I’d still recommend Array.Empty<T>() as the preferred method of retrieving empty arrays going forward.

With this, the earlier example can now be updated to simply return Array.Empty<byte>() directly:

public byte[] GenerateBytes()
{
    // Fast path for the empty case
    if (IsEmpty)
    {
        return Array.Empty<byte>();
    }

    // Actual operation here...
}

The fast path returns the same empty byte[] array instance as any other caller of Array.Empty<byte>().


Most uses of new T[0] throughout the .NET Framework libraries have been replaced with Array.Empty<T>() (uses of new T[0] that were missed for .NET Framework 4.6 have since been addressed in .NET Core, with a final use case still under discussion).

One interesting place where Array.Empty<T>() is now used is with params arrays.

Consider the following Log method:

public void Log(string message, params object[] args)
{
}

params allows Log to be called without specifying any args:

Log("Hello World!"); // no args passed

The C# compiler used in Visual Studio 2013 and earlier will compile this as:

Log("Hello World!", new object[0]);

The Roslyn C# compiler used in Visual Studio 2015 and later, on the other hand, will use Array.Empty<T>(), if it’s available:

Log("Hello World!", Array.Empty<object>());

Related: empty array enumerators are now cached.

Archive