"A property initializer that reads another property compiles headless but breaks the editor (CS0182)"
▸ SYMPTOM
- A C# property initializer that reads another property compiles clean under headless
dotnet build, then the editor compile fails withCS0182— "an attribute argument must be a constant expression." - The error points at a plain property, not at any attribute you wrote.
public int X { get; init; } = SomeClass.SomeProperty; // green headless, red in editor▸ CAUSE
The two builds diverge because the editor's code generator embeds property initializers in a position where only compile-time constants are legal. A headless dotnet build never runs that generator, so it accepts a non-constant initializer that the editor later rejects.
The consequence is the familiar trap: a green dotnet build is not proof the editor will accept the same source. This is the same headless-green / editor-red split seen in the razor codegen traps — except here it fires on a plain C# property, not markup.
▸ FIX
Give the property a constant sentinel default and resolve it to the live value at a validation/normalization boundary instead of at declaration time:
public int X { get; init; } = -1; // -1 = "unset", a compile-time constant
// later, at your validation / normalization step:
if (X == -1)
X = SomeClass.SomeProperty;0 or -1 (whatever value can't be a legitimate input) works as the "unset" marker. The declaration now contains only a constant, so the editor's generator is happy, and the real value is filled in where running code — not codegen — can evaluate it.
▸ WHY IT WORKS
The editor and headless builds run different codegen, so "it builds" from the command line can hide an editor-only failure. Defaulting fields to constants and resolving them at a boundary keeps both compilers happy and moves the live lookup to a place where it's actually legal to run.
- Published.