Summary
Decompiling a ReadOnlySpan<T> that is created from an array literal on a target framework without RuntimeHelpers.CreateSpan (e.g. .NET Framework, or netstandard2.0 + the System.Memory package) produces code that does not recompile: it references the compiler-synthesized <PrivateImplementationDetails> cache field, which is never declared and whose angle-bracket name is not expressible in C#.
Input code
Compiled in Release targeting .NET Framework 4.6.2 with the System.Memory package:
using System;
public static class SpanCache
{
public static ReadOnlySpan<char> NewLine => new char[] { '\r', '\n' };
}
On such a framework Roslyn caches the backing char[] in a lazy <PrivateImplementationDetails> static field (there is no RuntimeHelpers.CreateSpan to blit the data at run time).
Erroneous output
public static ReadOnlySpan<char> NewLine
{
get
{
object obj = <PrivateImplementationDetails>.B7F5...7075_A1;
if (obj == null)
{
obj = new char[2] { '\r', '\n' };
<PrivateImplementationDetails>.B7F5...7075_A1 = (char[])obj;
}
return new ReadOnlySpan<char>((char[])obj);
}
}
The synthesized <PrivateImplementationDetails> type is referenced but never declared, and its name is not expressible in C#. In whole-project output the name is escaped to _003CPrivateImplementationDetails_003E, which then fails to compile with CS0400 (the type is undeclared).
Expected output
The array literal should be recovered, so the <PrivateImplementationDetails> reference disappears — the same result the RuntimeHelpers.CreateSpan path (net7+) already produces:
public static ReadOnlySpan<char> NewLine => new ReadOnlySpan<char>(new char[2] { '\r', '\n' });
Details
- Product in use: ICSharpCode.Decompiler
- Version in use: master
fe50c00f (11.0.0.9086); also reproduces on 10.1 (10.1.0.8386).
- Real-world occurrence: Markdig
CodeInlineParser (net462).
- Root cause: ILSpy recovers the modern
RuntimeHelpers.CreateSpan form (TransformArrayInitializers.TransformRuntimeHelpersCreateSpanInitialization) but not the legacy lazy-char[]-cache form, so the raw <PrivateImplementationDetails> field reference is left behind. This lazy cache has the same shape as the anonymous-method delegate cache already collapsed by CachedDelegateInitialization.
- I'm happy to contribute a fix.
Summary
Decompiling a
ReadOnlySpan<T>that is created from an array literal on a target framework withoutRuntimeHelpers.CreateSpan(e.g. .NET Framework, or netstandard2.0 + the System.Memory package) produces code that does not recompile: it references the compiler-synthesized<PrivateImplementationDetails>cache field, which is never declared and whose angle-bracket name is not expressible in C#.Input code
Compiled in Release targeting .NET Framework 4.6.2 with the
System.Memorypackage:On such a framework Roslyn caches the backing
char[]in a lazy<PrivateImplementationDetails>static field (there is noRuntimeHelpers.CreateSpanto blit the data at run time).Erroneous output
The synthesized
<PrivateImplementationDetails>type is referenced but never declared, and its name is not expressible in C#. In whole-project output the name is escaped to_003CPrivateImplementationDetails_003E, which then fails to compile with CS0400 (the type is undeclared).Expected output
The array literal should be recovered, so the
<PrivateImplementationDetails>reference disappears — the same result theRuntimeHelpers.CreateSpanpath (net7+) already produces:Details
fe50c00f(11.0.0.9086); also reproduces on 10.1 (10.1.0.8386).CodeInlineParser(net462).RuntimeHelpers.CreateSpanform (TransformArrayInitializers.TransformRuntimeHelpersCreateSpanInitialization) but not the legacy lazy-char[]-cache form, so the raw<PrivateImplementationDetails>field reference is left behind. This lazy cache has the same shape as the anonymous-method delegate cache already collapsed byCachedDelegateInitialization.