Summary
When a Blazor component [Parameter] is typed as a C# union (.NET 11) that includes string as a case, the Razor literal-attribute shortcut does not apply — the attribute value is parsed as a C# expression instead of a string literal. This makes the canonical "Slot accepts text or markup or fragment" scenario much less ergonomic than it needs to be.
Repro
Slot.razor:
@switch (Content.Value)
{
case string text: @text; break;
case MarkupString markup: @markup; break;
case RenderFragment fragment: @fragment; break;
}
@code {
[Parameter] public Slot Content { get; set; }
}
Slot is public union Slot(string, MarkupString, RenderFragment);.
@* Does NOT compile — `hello` is parsed as the C# expression `hello` *@
<Slot Content="hello" />
@* Workaround — author must wrap every literal in @("…") *@
<Slot Content="@("hello")" />
Why this matters
This is the headline scenario for C# unions in Blazor: a component-library author exposes a single "slot" parameter that uniformly accepts plain text, raw HTML (MarkupString), or templated content (RenderFragment). The implicit conversions synthesized by the union language feature already make the typed call site ((Slot)"hello") work; only the Razor markup form is missing.
Verified on .NET 11 Preview 6 (11.0.100-preview.6.26315.102) via danroth27/blazor-unions-verify — see Pages/LiteralAttrHost.razor and RazorCompilerEdgeCaseTests.StringLiteralAttribute_OnUnionParameter_TreatedAsExpression.
Today's behaviour
Razor's literal-attribute shortcut is gated on the parameter type being exactly string. Any other declared type triggers the C# expression path, so Value="hello" becomes Value = hello and fails to compile.
Proposed fix
Extend the eligibility check so that a parameter typed as a C# union (a struct implementing IUnion) is eligible for the literal-attribute shortcut if the union has a string case. When emitting the attribute assignment, cast the literal through the union type so the compiler-synthesized implicit conversion runs:
// Today (string parameter type):
__builder.AddComponentParameter(1, "Value", (object)"hello");
// Proposed (union[string] parameter type):
__builder.AddComponentParameter(1, "Value", (object)(Slot)"hello");
This is a Razor-compiler-only change — no framework API changes, no runtime changes. It matches the parallel C# language design where (Slot)"hello" already compiles.
Context
Summary
When a Blazor component
[Parameter]is typed as a C# union (.NET 11) that includesstringas a case, the Razor literal-attribute shortcut does not apply — the attribute value is parsed as a C# expression instead of a string literal. This makes the canonical "Slot accepts text or markup or fragment" scenario much less ergonomic than it needs to be.Repro
Slot.razor:Slotispublic union Slot(string, MarkupString, RenderFragment);.Why this matters
This is the headline scenario for C# unions in Blazor: a component-library author exposes a single "slot" parameter that uniformly accepts plain text, raw HTML (
MarkupString), or templated content (RenderFragment). The implicit conversions synthesized by the union language feature already make the typed call site ((Slot)"hello") work; only the Razor markup form is missing.Verified on .NET 11 Preview 6 (
11.0.100-preview.6.26315.102) via danroth27/blazor-unions-verify — seePages/LiteralAttrHost.razorandRazorCompilerEdgeCaseTests.StringLiteralAttribute_OnUnionParameter_TreatedAsExpression.Today's behaviour
Razor's literal-attribute shortcut is gated on the parameter type being exactly
string. Any other declared type triggers the C# expression path, soValue="hello"becomesValue = helloand fails to compile.Proposed fix
Extend the eligibility check so that a parameter typed as a C# union (a struct implementing
IUnion) is eligible for the literal-attribute shortcut if the union has astringcase. When emitting the attribute assignment, cast the literal through the union type so the compiler-synthesized implicit conversion runs:This is a Razor-compiler-only change — no framework API changes, no runtime changes. It matches the parallel C# language design where
(Slot)"hello"already compiles.Context