Steps to reproduce
When decompiling a class that contains a C#-style field-like event with an
explicit add / remove accessor pair (the kind the compiler emits when
the source uses Interlocked.CompareExchange for thread-safe subscribe), the
private backing field for the event is emitted with two consecutive
[CompilerGenerated] attributes, which fails to compile with CS0579.
The shape that triggers it: a type with two unrelated field-like events whose
backing fields are typed as EventHandler and accessed inside the explicit
add/remove via the Interlocked.CompareExchange(ref _backingField, ...)
idiom.
Minimal repro source (compile with C# 7.3, then re-decompile):
using System;
using System.Threading;
public class Repro
{
public event EventHandler Changed;
public event EventHandler LengthChanged;
public void RaiseChanged() => Changed?.Invoke(this, EventArgs.Empty);
public void RaiseLength() => LengthChanged?.Invoke(this, EventArgs.Empty);
}
Compile this against .NET Framework 4.8, then run:
ilspycmd Repro.dll -p -lv CSharp7_3 -o ./out
Erroneous output (Repro.cs)
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
public class Repro
{
[CompilerGenerated]
[CompilerGenerated] // <-- DUPLICATE
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private EventHandler Changed;
[CompilerGenerated]
[CompilerGenerated] // <-- DUPLICATE
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private EventHandler LengthChanged;
// ... add/remove pair using Interlocked.CompareExchange ...
}
Roslyn rejects this with two errors:
Repro.cs(8,3): error CS0579: Duplicate 'CompilerGenerated' attribute
Repro.cs(13,3): error CS0579: Duplicate 'CompilerGenerated' attribute
Expected output
A single [CompilerGenerated] per backing field, matching what the compiler
originally emitted.
Notes
The IL contains the attribute exactly once on each backing field row — this
is purely a duplication in the decompiler's emit path. The attribute appears
to be added by both the generic field-attribute path and the
"this looks like a field-like event backing field" reconstruction path
without checking whether the other already added it.
Possibly related (closed): #3575 — "Uses of compiler-generated events getting
swapped with non-generated events of same type" — the fix landed in
commit 7b03606 (Oct 2025) but does not cover this specific shape (two
unrelated events on the same type, each with their own backing field, both
using the explicit Interlocked.CompareExchange add/remove pattern).
Details
- Product in use: ilspycmd
- Version in use: 10.0.0.8330 (latest stable as of 2026-04-13)
- Target assembly is a .NET Framework 4.8 class library / WinForms application
Steps to reproduce
When decompiling a class that contains a C#-style field-like event with an
explicit
add/removeaccessor pair (the kind the compiler emits whenthe source uses
Interlocked.CompareExchangefor thread-safe subscribe), theprivate backing field for the event is emitted with two consecutive
[CompilerGenerated]attributes, which fails to compile withCS0579.The shape that triggers it: a type with two unrelated field-like events whose
backing fields are typed as
EventHandlerand accessed inside the explicitadd/remove via the
Interlocked.CompareExchange(ref _backingField, ...)idiom.
Minimal repro source (compile with C# 7.3, then re-decompile):
Compile this against .NET Framework 4.8, then run:
Erroneous output (
Repro.cs)Roslyn rejects this with two errors:
Expected output
A single
[CompilerGenerated]per backing field, matching what the compileroriginally emitted.
Notes
The IL contains the attribute exactly once on each backing field row — this
is purely a duplication in the decompiler's emit path. The attribute appears
to be added by both the generic field-attribute path and the
"this looks like a field-like event backing field" reconstruction path
without checking whether the other already added it.
Possibly related (closed): #3575 — "Uses of compiler-generated events getting
swapped with non-generated events of same type" — the fix landed in
commit 7b03606 (Oct 2025) but does not cover this specific shape (two
unrelated events on the same type, each with their own backing field, both
using the explicit
Interlocked.CompareExchangeadd/remove pattern).Details