Skip to content

Duplicate [CompilerGenerated] attribute emitted on field-like event backing fields (CS0579) #3716

@SBvn-dev

Description

@SBvn-dev

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions