Skip to content

[Proposal] Automated Setter method for Two-Way Binding and Default BindableProperties #345

@egvijayanand

Description

@egvijayanand

Feature name

Automated Setter method for Two-Way Binding and Default BindableProperties

Link to discussion

#343

Progress tracker

  • Android Implementation
  • iOS Implementation
  • MacCatalyst Implementation
  • Windows Implementation
  • Tizen Implementation
  • Unit Tests
  • Samples
  • Documentation

Summary

The Bind method behaves differently in the Typed Bindings approach compared to the classic String-based one.

In the classic approach, it is enough to specify the property to be bound. If the binding property's mode is two-way, the back update to the ViewModel property will happen automatically (similar to that of an XAML-based definition).

In the improved Typed Bindings approach, getters and setters are separated. Even for the default implementation, a setter method must be written to establish two-way data binding. This also didn't allow the use of default bindable properties.

A solution has now been found to address this gap using C# Expressions, without affecting performance or incurring runtime overhead as with reflection.

The resulting behavior will align with the classic model while being as extensible as Typed Binding.

The implementation will only take effect if the user has not defined a setter method. So it's backward compatible.

Motivation

Similar binding behavior across all approaches, permitting skill reuse.

Detailed Design

Defining a default setter method using C# Expressions:

A working sample is available here - https://github.com/egvijayanand/markup-issue-272

if (setter is null)
{
    // Include the setter only if it is defined as two-way or if the mode is overridden.
    if (targetProperty.DefaultBindingMode == BindingMode.TwoWay || mode == BindingMode.TwoWay)
    {
        // Already defined in the toolkit to retrieve the member name - To be reused
        // Assuming MemberExpression for sample
        var propertyName = ((MemberExpression)getter.Body).Member.Name;
        var param1 = Expression.Parameter(typeof(TBindingContext), "context");
        var param2 = Expression.Parameter(typeof(TSource), "value");
        var memExp = Expression.Property(param1, propertyName);
        var assignExp = Expression.Assign(memExp, param2);
        // TODO: Need to check whether the action can be a static lambda
        var action = Expression.Lambda<Action<TBindingContext, TSource>>(assignExp, [param1, param2]).Compile();

        // Structure of the generated definition
        // setter = (TBindingContext context, TSource value) => context.Property = value;
        setter = action;
    }
}

Usage Syntax

// As you can observe, the resulting code is quite simple. Easy to understand.
// This will improve the overall productivity

// Existing

// Requires the target property even if it is the default one, and a setter definition if it is two-way binding.
new Picker().Bind(Picker.SelectedIndexProperty, static (MyViewModel vm) => vm.Index, static (MyViewModel vm, int value) => vm.Index = value);

// Proposed

// SelectedIndexProperty is the default bindable property for Picker control
// It's two-way binding by default
new Picker().Bind(static (MyViewModel vm) => vm.Index);

Drawbacks

I don't think of any as it's compiled. Maybe a different perspective can fill-in.

Alternatives

Unresolved Questions

Static lambda for the setter action method to maintain the performance.

Explore the docs to ascertain whether the compiled method is static or if any further definition is required to make it static.

Metadata

Metadata

Labels

approvedchampionA member of the .NET MAUI Toolkit core team has chosen to champion this featureproposalA fully fleshed out proposal describing a new feature in syntactic and semantic detail

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions