Skip to content

[XEXPR] [Experimental] XAML C# Expressions#33693

Merged
rmarinho merged 4 commits intomainfrom
dev/stdelc/xaml+code-clean
Feb 4, 2026
Merged

[XEXPR] [Experimental] XAML C# Expressions#33693
rmarinho merged 4 commits intomainfrom
dev/stdelc/xaml+code-clean

Conversation

@StephaneDelcroix
Copy link
Contributor

@StephaneDelcroix StephaneDelcroix commented Jan 23, 2026

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

XAML C# Expressions

Write C# expressions directly in XAML — no more converters, no more boilerplate.

⚠️ Experimental Feature

This is an experimental preview feature for .NET 11. The API and syntax may change before release.

How to Enable

Add to your project file:

<PropertyGroup>
    <EnablePreviewFeatures>true</EnablePreviewFeatures>
</PropertyGroup>

Without this flag, using C# expression syntax results in error MAUIX2012.

Requirements

  • SourceGen mode (default in .NET 11)
  • x:DataType attribute on your page/view

Example

<ContentPage x:DataType="local:ProductViewModel">
    
    <!-- Simple binding -->
    <Label Text="{ProductName}" />
    
    <!-- String interpolation -->
    <Label Text="{$'{Quantity}x {ProductName}'}" />
    
    <!-- Calculations -->
    <Label Text="{$'Total: ${Price * Quantity:F2}'}" />
    
    <!-- Boolean negation (no converter needed!) -->
    <Label IsVisible="{!IsLoading}" />
    
    <!-- Boolean expressions (no MultiBinding needed!) -->
    <Button Text="Submit" 
            IsEnabled="{HasAccount &amp;&amp; AgreedToTerms}" />
    
    <!-- Inline event handlers (no code-behind needed!) -->
    <Button Text="{$'Clicked {ClickCount} times'}" 
            Clicked="{(s, e) => ClickCount++}" />
    
    <!-- Ternary expressions -->
    <Label Text="{IsVip ? 'Gold Member' : 'Standard'}" />
    
</ContentPage>

Syntax Highlights

Some of the supported expressions (see full spec for complete reference):

Feature Syntax Example
Property binding {Property} {Username}
Nested property {A.B} {User.DisplayName}
String interpolation {$'..{x}..'} {$'Hello {Name}!'}
Boolean negation {!Bool} {!IsHidden}
Boolean AND/OR {A && B} {IsLoaded &amp;&amp; HasData}
Arithmetic {A * B} {Price * Quantity}
Ternary {c ? a : b} {IsVip ? 'Gold' : 'Standard'}
Null-coalesce {a ?? b} {Title ?? 'Untitled'}
Lambda events {(s, e) => ...} {(s, e) => Count++}
Local method {Method()} {GetDisplayText()}
Static member {Type.Member} {DateTime.Now}

What's NOT Supported

  • Async lambdas ({async (s, e) => ...}) — use regular methods
  • Parameterless lambdas ({() => ...}) — must include (s, e)
  • XamlC or Runtime inflation — SourceGen only

Resources

Copilot AI review requested due to automatic review settings January 23, 2026 16:17
@StephaneDelcroix StephaneDelcroix added this to the .NET 11.0-preview1 milestone Jan 23, 2026
@StephaneDelcroix StephaneDelcroix force-pushed the dev/stdelc/xaml+code-clean branch from 64db388 to 507dc00 Compare January 23, 2026 16:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces XAML C# Expressions, an experimental SourceGen-only feature that enables embedding C# expressions directly in XAML property values. The feature provides automatic detection of whether identifiers reference this (page/view) or x:DataType (BindingContext) members, generating either compile-time SetValue calls or runtime TypedBinding instances with INPC handlers.

Changes:

  • New SourceGen infrastructure for detecting and processing C# expressions in XAML markup
  • Automatic member resolution distinguishes between local (this) and binding (x:DataType) properties
  • Support for lambda event handlers (including async) and string interpolation
  • Comprehensive test coverage (63 tests total) with diagnostic error scenarios
  • Full specification and documentation for the experimental feature

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
CSharpExpressions.sgen.xaml.cs Comprehensive test suite with 46 integration tests covering expressions, bindings, events, and diagnostics
CSharpExpressions.sgen.xaml XAML test file demonstrating all expression syntax variants
MemberResolverTests.cs 14 unit tests for member resolution logic (this vs x:DataType)
CSharpExpressionDiagnosticsTests.cs 4 diagnostic tests for ambiguous members and not-found errors
XDataTypeResolver.cs Helper to extract x:DataType from XAML element tree (extracted from existing code)
ExpandMarkupsVisitor.cs Expression detection in markup visitor with disambiguation logic
SetPropertyHelpers.cs TypedBinding generation for expressions and lambda event handler support
NodeSGExtensions.cs Extension methods to handle Expression values during code generation
MemberResolver.cs Core logic for resolving identifiers to this/x:DataType with explicit prefix support
ExpressionAnalyzer.cs Roslyn-based expression analysis extracting INPC handlers and local captures
Descriptors.cs Three new diagnostic descriptors (MAUIX2007, MAUIX2008, MAUIX2009)
CSharpExpressionHelpers.cs Detection and transformation utilities (quote conversion, lambda detection)
AnalyzerReleases.Unshipped.md Analyzer release notes for new diagnostics
XamlCSharpExpressions.md Complete specification with syntax reference and examples
xaml-csharp-expressions.instructions.md GitHub Copilot instructions for using the feature

@kcrg
Copy link

kcrg commented Jan 23, 2026

R.I.P InvertedBoolConverter 2014-2026 💀

@jaimeatsherpa
Copy link

Awesome!

@albyrock87
Copy link
Contributor

This is a game changer!
Thank you!

@dartasen
Copy link
Contributor

dartasen commented Jan 24, 2026

Even in my wildest dreams, that didn't exist

Awesome work

@PySom
Copy link

PySom commented Jan 24, 2026

thank you, please approve like yesterday!!!
@StephaneDelcroix please could @ be used to start the c# expression within the string? More like razor (eg <Label Text="@Product.Name" />). It's so that the C# is consistent. Here, I see that single quote is used for a string. Having to learn something extra, specific to this may be difficult.

@MartyIX
Copy link
Contributor

MartyIX commented Jan 24, 2026

One word: This is great!1

Footnotes

  1. Send me back to school! 😄

@AmSmart
Copy link

AmSmart commented Jan 24, 2026

This is awemazing, improving Xaml DevX is the biggest thing anyone can do for MAUI. I've dreamt of days like this.

@sonnemaf
Copy link

Can we have this in WPF, UWP and WinUI3 too? It is amazing.

@albyrock87
Copy link
Contributor

Having async void in event handlers is generally a bad thing.
I was wondering if behind the scenes there could be the FireAndForget extension method MAUI has for this kind of situations where (if available) an ILogger is being used in case of a thrown exception, but at the same time, I feel it would be probably better to not support async/await in events so that the developer is forced to think about it.
Like to me it seems this may encourage bad practices.

@StephaneDelcroix StephaneDelcroix force-pushed the dev/stdelc/xaml+code-clean branch from 2719862 to a296814 Compare January 26, 2026 10:42
@michalpobuta
Copy link
Contributor

This is a game-changer! This is exactly the direction .NET MAUI needs to take

@egvijayanand
Copy link
Contributor

egvijayanand commented Jan 26, 2026

Consider making it feature-complete by adding support for the OR operator too.

  • {A || B} - Boolean expressions (replaces MultiBinding + AnyTrueConverter)

@RedEye-Developers
Copy link

RedEye-Developers commented Jan 26, 2026

<Lable IsVisible={async () => {
          bool canVisible = false;
          white(true)
          {
                if(canVisible)
                {
                      canVisible = false;
                      return false;
                }
                
                 awite Task.Delay(TimeSpan.FromSecond(5));
                  canVisible = true;
                  return true;
          }
      }
}}

c# inside xmal.

@StephaneDelcroix
Copy link
Contributor Author

StephaneDelcroix commented Jan 26, 2026

@StephaneDelcroix please could @ be used to start the c# expression within the string?

We've consider using @, $ and even some emojis to indicate an expression. We choose to use {} to indicate, the XAML way, that some magic is happening somewhere, and {=... } to disambiguate in rare case of conflicts between an expression and a markup extension.

We're not creating a new language here, and keeping backward compatibility is our first priority. This is why Text="@somevalue" didn't make the cut, as there is no way to know if the user expect a string starting with @ or an evaluate an expression.

@StephaneDelcroix
Copy link
Contributor Author

StephaneDelcroix commented Jan 26, 2026

@egvijayanand This already works! Any valid C# expression is supported, including ||, &&, and any other operators.

<Label IsVisible="{A || B}" />
<Label IsVisible="{IsLoaded && ShowContent}" />

@StephaneDelcroix StephaneDelcroix changed the title [XREF] [Experimental] XAML C# Expressions [XEXPR] [Experimental] XAML C# Expressions Jan 26, 2026
@egvijayanand
Copy link
Contributor

egvijayanand commented Jan 26, 2026

This already works!

Great to hear about it 👍.

@jonathanpeppers
Copy link
Member

Is there a way we can avoid this whole section?

image

Somehow Razor is able to do it:

image

@StephaneDelcroix
Copy link
Contributor Author

@jonathanpeppers razor doesn't have to be valid xml, we like that XAML stays processable by xml tools. For more complex expression that would otherwise require a lot of escaping (quotes, <, >, &), I'm adding support for CDATA

@PySom
Copy link

PySom commented Jan 27, 2026

@StephaneDelcroix please could @ be used to start the c# expression within the string?

We've consider using @, $ and even some emojis to indicate an expression. We choose to use {} to indicate, the XAML way, that some magic is happening somewhere, and {=... } to disambiguate in rare case of conflicts between an expression and a markup extension.

We're not creating a new language here, and keeping backward compatibility is our first priority. This is why Text="@somevalue" didn't make the cut, as there is no way to know if the user expect a string starting with @ or an evaluate an expression.

@StephaneDelcroix thank you for your response, your work and the addition of CDATA. With the addition of CDATA, it seems a bit too much. could everything that starts with {} be interpreted as CDATA content while "" will be as it always have been.

For instance
<Label Text={Product.Name} />
or <Label Text={"MAUI"} /> (a string is quoted)

<Label x:Name="label">
    <Label.IsVisible>{Count > 0 && Count < 100}</Label.IsVisible>
</Label>

<Button Clicked={(_, _) => label.Text = "Clicking"} />

<Label Title="{Binding Name}" /> (As XAML as always been)

This may not be valid XML
<Label Text={Name} /> I wish it can be
But this is valid

<Label>
   <Label.Text>{Name}</Label.Text>
</Label>

If everything within only {} is parsed as CDATA, the only pain point I can see is using property elements whenever we want to do c# expression

@StephaneDelcroix StephaneDelcroix added the area-xaml XAML, CSS, Triggers, Behaviors label Jan 27, 2026
@StephaneDelcroix StephaneDelcroix force-pushed the dev/stdelc/xaml+code-clean branch from f465454 to d72a9a9 Compare January 30, 2026 14:28
simonrozsival
simonrozsival previously approved these changes Jan 30, 2026
Copy link
Member

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I proposed several changes (esp. around auto escaping embedded C# code) but I believe we have room for breaking changes since this is an experimental feature. Let's move ahead and revisite it based on feedback from customers using it in real-world apps.

@StephaneDelcroix
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

return;

// Check if it's a C# expression
if (CSharpExpressionHelpers.IsExpression(trimmed, name => TryResolveMarkupExtensionType(name, node.NamespaceResolver)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any chance that existing, valid XAML, CSharpExpressionHelpers.IsExpression could return true?

If not, this seems reasonable to require $(EnablePreviewFeatures) to opt-in. $(EnablePreviewFeatures) is also viral, so if an assembly declares it, you have to declare it to use that assembly (or NuGet).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in doubt, if both a markup and an expression could be resolved, it'll error

@StephaneDelcroix
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

Experimental feature for .NET 11 - write C# expressions directly in XAML.

Features:
- Property bindings: {.PropertyName}, {this.PropertyName}
- String interpolation: {$'{First} {Last}'}
- Lambda event handlers: {(s, e) => DoSomething()}
- Compound expressions with operators
- AND/OR word aliases for XML compatibility
- Semantic char/string detection for single-quoted literals
- Custom markup extension semantic lookup

Diagnostics:
- MAUIX2007: Markup extension ambiguity warning
- MAUIX2008: Page/BindingContext member conflict error
- MAUIX2009: Member not found error
- MAUIX2010: Non-settable expression info
- MAUIX2012: Parameter list expression error
- MAUIX2013: Async lambda error
- Fix handling of escaped backslash followed by quote (e.g., '\"')
- Count consecutive backslashes to determine if quote is escaped
- Update C# version comment from 14 to 13
- Always transform single quotes to double quotes in GetExpressionCode
- TransformQuotesWithSemantics now converts single-char strings back to
  char literals where method parameters expect char type
- Removes unused TransformQuotes property from Expression record
- Fixes CS1012 (char literal) and CS1056 (unexpected $) build errors
C# Expressions only work with SourceGen mode, not Runtime or XamlC.
Changed test from [Theory] with [XamlInflatorData] to [Fact] that
explicitly uses XamlInflator.SourceGen.
@StephaneDelcroix StephaneDelcroix force-pushed the dev/stdelc/xaml+code-clean branch from 4683c74 to b9bd7a4 Compare February 4, 2026 09:29
@rmarinho
Copy link
Member

rmarinho commented Feb 4, 2026

/azp run maui-pr-devicetests

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@rmarinho rmarinho merged commit 64d90e3 into main Feb 4, 2026
36 of 42 checks passed
@rmarinho rmarinho deleted the dev/stdelc/xaml+code-clean branch February 4, 2026 16:24
StephaneDelcroix added a commit that referenced this pull request Feb 9, 2026
XAML C# Expressions were introduced in #33693 as an experimental feature
gated behind EnablePreviewFeatures. For .NET 11, this feature graduates
to GA, so the gating is no longer needed.

Changes:
- Remove MAUIX2012 diagnostic (CSharpExpressionsRequirePreviewFeatures)
- Remove EnablePreviewFeatures checks in ExpandMarkupsVisitor
- Remove EnablePreviewFeatures property from ProjectItem
- Remove CompilerVisibleProperty for EnablePreviewFeatures from targets
- Update test infrastructure to remove EnablePreviewFeatures parameter
- Remove EnablePreviewFeatures from test project files
@eduardoagr
Copy link

They should sustitute &amp for and

@egvijayanand
Copy link
Contributor

egvijayanand commented Feb 16, 2026

This expression works fine for regular properties, but does not detect command properties. Is there any specific reason?

Error message: Property or indexer 'MainViewModel.IncrementCommand' cannot be assigned to -- it is read only.

It seems that it tries to assign a value to the read-only Command property. In general, command properties are intended to be read-only, featuring only a getter.

Suggest creating a discussion so that feedback can be viewed in one place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-xaml XAML, CSS, Triggers, Behaviors

Projects

None yet

Development

Successfully merging this pull request may close these issues.