microsoft-ui-xaml icon indicating copy to clipboard operation
microsoft-ui-xaml copied to clipboard

x:Bind does not work with C# record

Open Marv51 opened this issue 4 years ago • 21 comments

Describe the bug I am try to x:bind a c# 9 record to the ItemsSource of an ItemsRepeater, with a corresponding DataTemplate. However, compilation fails with

XamlTypeInfo.g.cs(6465,13,6465,23): error CS8852: Init-only property or indexer 'BlogPost.Title' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.

Steps to reproduce the bug

<ItemsRepeater ItemsSource="{x:Bind BlogPosts}">
    <ItemsRepeater.ItemTemplate>
        <DataTemplate x:DataType="local:BlogPost">
                <TextBlock>{x:Bind Title}</TextBlock>
        </DataTemplate>
    </ItemsRepeater.ItemTemplate>
</ItemsRepeater>
public record BlogPost(string Title, string Teaser, Uri? Url, DateTime Published);

public partial class MyPage: Page, INotifyPropertyChanged
{
    public ObservableCollection<BlogPost> BlogPosts { get; } = new()
    {
        new BlogPost("Test1", "Testing", null, DateTime.Now),
        new BlogPost("Test2", "Testing2", null, DateTime.Now),
        new BlogPost("Test2", "Testing2", null, DateTime.Now),
    };
// shortened
}

Version Info

NuGet package version: WinUI 3 - Windows App SDK 0.8: 0.8.0

Windows app type:

UWP Win32
Yes
Windows 10 version Saw the problem?
Insider Build (xxxxx)
21H1 (19043) Yes
October 2020 Update (19042)
May 2020 Update (19041)
November 2019 Update (18363)
May 2019 Update (18362)
October 2018 Update (17763)
April 2018 Update (17134)
Fall Creators Update (16299)
Creators Update (15063)
Device form factor Saw the problem?
Desktop Yes
Xbox
Surface Hub
IoT

Additional context The error message is referring to this generated method in XamlTypeInfo.g.cs which tries to set a property on a record:

private void set_295_BlogPost_Title(object instance, object Value)
{
    var that = (global::BrandMatrix.BlogPost)instance;
    that.Title = (global::System.String)Value;
}

Init Only Setters also seem to be broken in the same way:

 public class BlogPost {
        public string Title { get; init; }
        //...
    }

Of course private set; can be used as an alternative for now, but support for these features is needed to keep up with .net development. (Or at least a way better error message is needed)

Marv51 avatar Jun 29 '21 14:06 Marv51

I'm confused about why it's trying to call the setter in the first place? Have you set the defaultbindmode to TwoWay?

JohnnyWestlake avatar Jun 29 '21 19:06 JohnnyWestlake

@JohnnyWestlake I have not not set DefaultBindMode. The error occurs when compiling, the setter appears to be generated even though it is not used.

Marv51 avatar Jun 29 '21 20:06 Marv51

@RealTommyKlein FYI

StephenLPeters avatar Jun 29 '21 23:06 StephenLPeters

In WinAppSDK 1.0 with dotnet 6.0.1 the situation is still the same.

Marv51 avatar Jan 10 '22 13:01 Marv51

I've created a public readonly record struct in UWP and used the IsExternalInit polyfill method, and it works fine when binding. It seems odd that the generated code is trying to set the title for any reason.

@Marv51 Does the same thing occur if you setup the binding as <TextBlock Text="{x:Bind Title}"/> or make the struct readonly? (FYI you should always bind to the Text property directly to get proper graphical optimizations.)

hawkerm avatar Feb 14 '22 09:02 hawkerm

@hawkerm I tested this again just now. <TextBlock Text="{x:Bind Title}"/> results in the exact same error message. Even removing the TextBlock and just keeping the x:DataType results in the wrong code getting generated:

XamlTypeInfo.g.cs:

private void set_9_BlogPost_Title(object instance, object Value)
{
    var that = (global::App1.BlogPost)instance;
    that.Title = (global::System.String)Value;
}

Marv51 avatar Feb 14 '22 09:02 Marv51

Unfortunately this isn't an easy fix on the WinUI side - the metadata loading/reflection the Xaml compiler is pretty dated and can't access custom modifiers for types, and the System.Runtime.CompilerServices.IsExternalInit custom modifier is used to specify init properties and records. Because of this limitation, from the Xaml compiler's POV, the type has a valid public setter so it automatically generates the setter code, even when the setter is actually invalid due to being on a record or init property . We may be able to add better logic for detecting if a property is actually used in markup to mitigate this, but the real fix would be switching the Xaml compiler over to a more modern way of loading metadata, which wouldn't happen for Windows App SDK 1.1.

RealTommyKlein avatar Feb 14 '22 19:02 RealTommyKlein

@RealTommyKlein Thank you very much for explaining what is going on here and what the problems are.

Marv51 avatar Feb 14 '22 20:02 Marv51

@StephenLPeters @RealTommyKlein I can confirm this works fine in UWP, but with the same setup the XamlTypeInfo generator for WinUI 3 exhibits this issue w/ 1.0.3.

I just hit this building our new sample app in the Toolkit which we're cross-building for UWP and WinUI 3. The UWP head runs fine, but then the WinUI 3 compiler spits out the CS8852 error. I guess I don't understand why the XamlTypeInfo is even bothering to generate setters here at all if the property is only ever being read from in XAML (and not TwoWay bound)? Wouldn't that be the only case where the setter is required anyway?

michael-hawker avatar Apr 28 '22 23:04 michael-hawker

Hi there, any update on this? I can of course stop using records and switch back to readonly structs, but that's a bit sad. Thanks!

wokhan avatar Jun 16 '22 20:06 wokhan

Hi there, any update on this? I can of course stop using records and switch back to readonly structs, but that's a bit sad. Thanks!

Agreed.

michaelmelancon avatar Jun 16 '22 23:06 michaelmelancon

Still not working. I'd really like to use record classes. But I can't when running in WindowsAppSdk...

lukasf avatar Nov 19 '22 14:11 lukasf

I think this is still very important (re: #8638)

Marv51 avatar Jul 16 '23 14:07 Marv51

Here too. VERY important as providing a setter would make the messages which MUST be immutable mutable... . To have something "somehow" working I will write setters which will throw an Exception if called twice. But MS should take this issue more serious...

minesworld avatar Aug 14 '23 00:08 minesworld

I hit the property initializer issue today as well. That's quite an oversight I'd also like to see improved.

dotMorten avatar Jan 21 '24 04:01 dotMorten

Taking a quick look, I think the fix is to be made here:

https://github.com/microsoft/microsoft-ui-xaml/blob/aeed4c19e2fc7c2c093216a51b651f2f0890c930/src/src/XamlCompiler/BuildTasks/System.Xaml/System/Xaml/XamlMember.cs#L714-L722

See https://github.com/dotnet/runtime/issues/43088#issuecomment-704304906 for how to detect init-only using reflection.

This feels like an important scenario.

Youssef1313 avatar Aug 30 '24 16:08 Youssef1313

Also hit this issue today. Was looking to keep the data structure immutable.

adstep avatar Aug 30 '24 22:08 adstep

This issue is getting old and it's been blocking basic use cases. Since this is 100% reproducible, I believe that it's not a complex fix. Please consider reassigning this to higher priority. 🙂

AndrewKeepCoding avatar Oct 31 '24 04:10 AndrewKeepCoding

It is 2025 and the bug is still there...

petrochuk avatar Apr 21 '25 12:04 petrochuk

Hi I was excited that you guys tackled some XAML compiler improvements in WinAppSDK 1.7. Maybe somebody could make sure that this issues is properly prioritized? @RealTommyKlein do you have any new insights maybe?

That comment from @Youssef1313 really tickles me here, MethodInfo seems to definitely contain the info needed. I don't really want to think about the fact that this over 4 years old issue might have a < 5 lines of code fix.

Marv51 avatar Jul 24 '25 19:07 Marv51

Almost 2026, just hit this.

In my case I added a HashSet<TRecord> of a record to my model in c# only, no xaml reference. Changing it to a IReadOnlyList<TRecord> actually stops it from generating the record sets so 🤷.

DominicMaas avatar Nov 27 '25 21:11 DominicMaas