Initializing single-item collections on C#

CSharp interfaces are implementation contracts, enforcing that any implementation based on an interface MUST adhere to its requirements (properties, access modifiers, methods, etc.)

Playing around with a dead simple repository-like interface, it needs to retrieve instances of a given type and allow adding new ones. For simplicity I thought of making the Add() method accept an IEnumerable<T> as argument, so that it would be generic enough to allow adding multiple instance or just one.

public interface IDeadSimpleRepository 
{
    public void IAsyncEnumerable<string> GetAsync();
    public void ValueTask AddAsync(IEnumerable<string> items);
}

Because this is not mission critical, I thought:

ok, this reduces the interface's impact surface. If using it becomes cumbersome when adding a single instance, surely a extension method would do the job

But, what would perform better when creating an enumerable collection from a single instance? I though that Enumerable.Empty<T>.Append(T) would to the job, as technically does not create any object to be allocated. Which is true for Enumerable.Empty<T>, but not for the Append().

A quick and raw benchmark on .net 8 showed that creating a new array is twice as faster and allocates half memory than the Append() alternatives.

I also threw in there ArrayPool<string> simply to try it out. Although seemingly does not allocate, it gets more verbose on such a simple use case.

using System.Buffers;  
using BenchmarkDotNet.Attributes;  
using BenchmarkDotNet.Running;  
  
BenchmarkRunner.Run<SingleItemEnumerableBenchmark>();  
  
[SimpleJob(invocationCount: 10_000_000)]  
[MemoryDiagnoser]  
public class SingleItemEnumerableBenchmark  
{  
    private const string Value = nameof(SingleItemEnumerableBenchmark);  

    public uint DoNothing(IEnumerable<string> values) => 1;  
  
    [Benchmark(Baseline = true)]  
    public uint ArrayNew() => DoNothing(new []{Value});  
  
    [Benchmark]  
    public uint AppendEmptyEnumerable() =>  
        DoNothing(Enumerable  
            .Empty<string>()  
            .Append(Value)  
        );  

    [Benchmark]  
    public uint AppendArrayEmpty() =>  
        DoNothing(Array  
            .Empty<string>()  
            .Append(Value)  
        );  

    [Benchmark]  
    public uint ArrayPool()  
    {        
        var pool = ArrayPool<string>.Shared;  
        string[] array = pool.Rent(1);  
        array[0] = Value;  
        try  
        {  
            return DoNothing(array);  
        }        finally  
        {  
            pool.Return(array);  
        }    
    }
}
// * Summary *

BenchmarkDotNet v0.13.10, Ubuntu 22.04.3 LTS (Jammy Jellyfish)
Intel Core i7-4500U CPU 1.80GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
.NET SDK 8.0.100-rc.2.23502.2
  [Host]     : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
  Job-APTYLW : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2

InvocationCount=10000000  
Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
ArrayNew 8.327 ns 0.1395 ns 0.1236 ns 1.00 0.00 0.0153 32 B 1.00
AppendEmptyEnumerable 17.590 ns 0.1036 ns 0.0865 ns 2.11 0.03 0.0306 64 B 2.00
AppendArrayEmpty 15.326 ns 0.3325 ns 0.2947 ns 1.84 0.05 0.0306 64 B 2.00
ArrayPool 33.842 ns 0.1715 ns 0.1432 ns 4.06 0.06 - - 0.00

Turns out, the Append() LINQ extension method creates an instance of AppendPrepend1Iterator<TSource>[^1] which seems to be an internal IEnumerable<TSource> implementation that uses a linked list list approach to allow appending and traversing the enumerable.

Conclusion

Once again, LINQ methods make code readable and concise at the cost of performance. Who would have thought!

[^1]: Extension method implementation AppendPrepend.cs