question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Using Compiled Bindings to Properties of a Record Type Doesn't Fail Until Runtime

See original GitHub issue

Describe the bug If I use a compiled binding to a property of a record type, the compilation succeeds, only to get an InvalidProgramException at runtime when Avalonia attempts to set the property value.

To Reproduce

public record TestRecord(string Prop);

public class TestViewModel : ViewModelBase
{
    private TestRecord _testObj = new("");
    public TestRecord TestObj
    {
        get => _testObj;
        set = this.RaiseAndSetIfChanged(ref _testObj, value, nameof(TestObj));
    }
}
...
    <TextBox Text="{CompiledBinding TestObj.Prop}" />
...

Expected behavior This would refuse to compile.

Actual behavior It compiles, and Avalonia generates this IL at CompiledAvaloniaXaml.XamlIlHelpers.{path}.Name!Setter(object, object):

  .method compilercontrolled static void
    '{path}.Name!Setter'(
      [in] object obj0,
      [in] object obj1
    ) cil managed
  {
    .maxstack 2

    IL_0000: ldarg.0      // obj0
    IL_0001: castclass    {object type}
    IL_0006: ldarg        obj1
    IL_000a: castclass    [System.Runtime]System.String
    IL_000f: callvirt     instance void modreq ([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) {path}::set_Name(string)
    IL_0014: pop
    IL_0015: ret

  } // end of method XamlIlHelpers::'{path}.Name!Setter'

After the first two opcodes, there’s an object on the stack. The next two push a string onto the stack. Then the callvirt pops those two, leaving the stack empty. Then Avalonia threw in a random pop that would make a stack underflow before returning. Because of that random pop opcode, the runtime refuses to run that function and dotPeek refuses to decompile it.

Screenshots

Desktop

  • OS: Windows 11 and Raspbian Buster (not that it matters)
  • Version 0.10.15

Additional context Roslyn apparently generates public setters for record types, which Avalonia was able to bind to. Hence, why the compilation succeeded.

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
MrJulcommented, Aug 5, 2022

I’ve opened a PR to at least fix the InvalidProgramException, so compiled bindings don’t crash and work the same way as reflection bindings do currently.

Blocking init two-way/one-way-to-source bindings is another task, and if done, should be done both for compiled and reflection bindings. It depends on what the project maintainers think about it.

1reaction
MrJulcommented, Aug 5, 2022

The problem appears for init properties (which records use). Minimal repro:

public class TestClass { public string Prop { get; init; } }

<TestClass Prop="foo" />

It currently works with System.Reflection.Emit (loading xaml files at runtime) but not Cecil (build time). Making it work for Cecil seems easy enough: ReturnType.Name is modreq(IsExternalInit) void instead of void in this case, taking the underlying type if there’s a modreq/modopt should be enough. I can open a PR for that.

But if this runs, init setters then can be set several times, and they probably shouldn’t. IMO, an ideal solution would be to disallow two-way bindings (both compiled and reflection ones) to init properties, and only allow them in XAML setters when an object is being created, to mimic C# behavior. That can be considered a new feature though.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Compiled bindings - .NET MAUI
Therefore, any invalid bindings aren't detected until runtime when the application doesn't behave as expected or error messages appear. They ...
Read more >
Improving performance App using compiled bindings in ...
Compiled Binding help us to improve performance by resolving binding expressions at compile-time rather than runtime and integrating the IntelliSense, that's ...
Read more >
Why do I get compilation error when trying to use record ...
You need to double-check that the Code version you're using is correct, and that your project is correctly configured to use C# 9...
Read more >
bind config to C# records and classes with constructors
The package works on the 'fail fast' principle. Any non-nullable properties that cannot be set from configuration will result in an exception on ......
Read more >
Records
A record represents a collection of values stored together as one, where each component is identified by a different field name. The basic...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found