Skip to content

Unnecessary allocation for [element] expression #80656

@znakeeye

Description

@znakeeye

Targeting .NET 10. Consider the sample below. (Might be related to #78106.)

In a simple switch expression, I return an IEnumerable<char> for a bunch of known values. For unknown values, I want to fulfill the contract of the enumerable, so I use the nice collection expression syntax to return [c]. Looks nice, but on a very hot path, I noticed significant GC.

Question

Turns out that the collection expression allocates a temporary array, apparently for no reason. Shouldn't the optimizer/jitter be able to optimize away this allocation?

Sample project

Obviously a lot simplified compared to my real world example.

namespace SingleElementCollectionAllocation;

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GetCharacters("WHATEVER"));
    }

    private static IEnumerable<char> GetCharacters(string s)
    {
        return s.SelectMany(Unwanted_Collection_Allocation);
    }

    private static IEnumerable<char> Unwanted_Collection_Allocation(char c)
    {
        return c switch
        {
            'X' => DoubleUp(c),
            // Unwanted allocation here:
            // IL_000f:  newobj     instance void class '<>z__ReadOnlySingleElementList`1'<char>::.ctor(!0)
            _ => [c]
        };
    }
    
    private static IEnumerable<char> Ugly_Syntax_But_No_Collection_Allocation(char c)
    {
        return c switch
        {
            'X' => DoubleUp(c),
            _ => SingleElementHelper(c)
        };
    }

    private static IEnumerable<char> SingleElementHelper(char c)
    {
        yield return c;
    }

    private static IEnumerable<char> DoubleUp(char c)
    {
        yield return c;
        yield return c;
    }
}

Generated IL code

.method private hidebysig static class [System.Runtime]System.Collections.Generic.IEnumerable`1<char> 
        Unwanted_Collection_Allocation(char c) cil managed
{
  // Code size       23 (0x17)
  .maxstack  2
  .locals init (class [System.Runtime]System.Collections.Generic.IEnumerable`1<char> V_0)
  IL_0000:  ldarg.0
  IL_0001:  ldc.i4.s   88
  IL_0003:  bne.un.s   IL_000e
  IL_0005:  ldarg.0
  IL_0006:  call       class [System.Runtime]System.Collections.Generic.IEnumerable`1<char> SingleElementCollectionAllocation.Program::DoubleUp(char)
  IL_000b:  stloc.0
  IL_000c:  br.s       IL_0015
  IL_000e:  ldarg.0
  IL_000f:  newobj     instance void class '<>z__ReadOnlySingleElementList`1'<char>::.ctor(!0)
  IL_0014:  stloc.0
  IL_0015:  ldloc.0
  IL_0016:  ret
} // end of method Program::Unwanted_Collection_Allocation

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions