Skip to content

[Feature] Support x:Code directive for inline C# in XAML #34712

@StephaneDelcroix

Description

@StephaneDelcroix

Summary

Add support for the x:Code XAML directive, allowing developers to define C# members (methods, fields, properties) directly inside XAML files without a separate code-behind file.

This becomes a natural complement to XAML C# Expressions (XEXPR). Where XEXPR enables inline expressions in attribute values ({= expr}, lambdas, etc.), x:Code enables inline member declarations — event handlers, helper methods, backing fields — completing the "single-file component" story.

Motivation

With XEXPR, developers can already write:

<Button Text="Add" Clicked="{(s, e) => Items.Add(new Item())}" />

But anything beyond a one-liner forces a code-behind file. x:Code closes the gap:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyApp.MainPage">

    <x:Code><![CDATA[
        ObservableCollection<Item> Items { get; } = new();

        void OnRefresh(object sender, EventArgs e)
        {
            Items.Clear();
            foreach (var item in DataService.Load())
                Items.Add(item);
        }
    ]]></x:Code>

    <VerticalStackLayout>
        <Button Text="Refresh" Clicked="{OnRefresh}" />
        <CollectionView ItemsSource="{Items}" />
    </VerticalStackLayout>
</ContentPage>

Scenarios unlocked

Scenario Today With x:Code
Simple event handler Requires .xaml.cs file Inline in XAML
Bindable helper property Requires .xaml.cs file Inline in XAML
Self-contained sample/snippet Two files minimum Single .xaml file
DataTemplates with per-template logic External static helpers Colocated code

Hot Reload

Hot Reload is supported through the existing C# Hot Reload infrastructure. Since x:Code blocks are emitted as regular members of the partial class by the source generator, edits to x:Code content are standard C# member changes from the compiler's perspective and flow through the C# HR pipeline.

Constraints (same as WPF + MAUI-specific)

WPF-inherited constraints

These match the WPF x:Code specification:

  1. x:Code must be an immediate child element of the root element.
  2. x:Class must be provided on the root element.
  3. Code is emitted into the scope of the existing partial class — all declarations become members/variables of that class.
  4. Cannot define additional top-level classes (nesting is allowed but atypical).
  5. Cannot define or add CLR namespaces beyond the partial class namespace — external types must be fully qualified (or use using aliases inside the block if the language supports it).
  6. Conflicts with code-behind members are compiler errors — standard partial class rules apply.

MAUI-specific constraints

  1. Requires XAML Source Generator (XSG)x:Code is not supported by runtime XAML inflation or XamlC IL rewriting. Only the source generator pipeline can emit the code block into the generated partial class.
  2. Requires <EnablePreviewFeatures>true</EnablePreviewFeatures> — same gating as XEXPR (diagnostic MAUIX2012 when missing).
  3. Content should be wrapped in <![CDATA[...]]> — recommended to avoid XML-escaping <, >, & in C# code. The parser already handles CDATA nodes.
  4. Multiple x:Code blocks are allowed — each is concatenated into the partial class body (order-preserved). This keeps code colocated with the UI section it supports.

Interaction with XEXPR

x:Code and XEXPR are complementary:

  • XEXPR → inline expressions in attribute values (bindings, event lambdas, computed values)
  • x:Code → member declarations (methods, properties, fields)

Members declared in x:Code are accessible from XEXPR expressions via this. (or bare identifier if unambiguous), following existing XEXPR resolution rules (MAUIX2007/2008 diagnostics for ambiguity).

Diagnostics

Code Condition
MAUIX2012 EnablePreviewFeatures not set (reuse existing)
New x:Code not an immediate child of root element
New x:Code used without x:Class on root
New x:Code used with runtime or XamlC inflator (XSG required)

Open questions

1. x:Code in XAML without x:Class (ResourceDictionary)

Today the source generator already compiles XAML files without an explicit x:Class (primarily ResourceDictionary files) by synthesizing a temporary class. Should x:Code be supported in that scenario?

Arguments for: A ResourceDictionary with value converters or helper methods defined inline via x:Code would be fully self-contained — no satellite .cs file needed.

Arguments against: The synthesized class is an implementation detail with no stable identity; members declared in x:Code would exist on a type that consumers can't reference. This may create confusion about member visibility and testability.

Tentative answer: Defer — require x:Class initially (constraint #2). Revisit if there's demand for self-contained RD files.

2. Follow-up: make .xaml.cs code-behind optional

With x:Code, the code-behind file becomes redundant for simple pages. As a follow-up, the source generator should emit a complete code-behind (including constructor and InitializeComponent() call) when no .xaml.cs file is present — making true single-file XAML components possible:

<!-- No .xaml.cs file needed -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyApp.SimplePage">

    <x:Code><![CDATA[
        void OnTap(object sender, EventArgs e) => Title = "Tapped!";
    ]]></x:Code>

    <Button Text="Tap me" Clicked="{OnTap}" />
</ContentPage>

This is a separate work item that depends on x:Code landing first.

3. IDE integration (code completion, diagnostics)

x:Code blocks contain C# code embedded inside XAML (typically inside <![CDATA[...]]>). The source generator will emit this code verbatim into a partial class, so compiler diagnostics will surface — but with mapped locations pointing into the XAML file, which IDEs may or may not handle gracefully.

Key questions:

  • Code completion: Can VS / VS Code provide IntelliSense inside x:Code CDATA blocks? This likely requires IDE-side awareness of the embedded language context (similar to how Razor handles @code {} blocks). Without it, developers get correctness (compiler errors) but lose discoverability.
  • Syntax highlighting: XAML editors currently treat CDATA as opaque text. C# highlighting inside x:Code would require editor extensions or language server cooperation.
  • Error mapping: The source generator can emit #line directives to map errors back to XAML line numbers. This works for compiler errors but may not integrate with IDE inline error display inside CDATA.

Tentative answer: Ship x:Code without IDE-specific tooling initially — compiler errors and HR work out of the box. IDE richness (completion, highlighting) is a tooling follow-up that can be pursued independently, potentially by the VS XAML Language Service team.

Non-goals

  • x:Code in non-root elements (e.g., inside DataTemplates) — out of scope for initial implementation.
  • Multi-language support — C# only, matching XEXPR.
  • Runtime interpretation — compilation only, no eval/scripting.

Metadata

Metadata

Labels

s/triagedIssue has been reviewed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions