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.

[Proposal] Typed Bindings

See original GitHub issue

Feature name

Typed Bindings

Link to discussion

https://github.com/CommunityToolkit/Maui.Markup/discussions/154#discussioncomment-4210325

Progress tracker

Summary

Typed Bindings are used by XAML Compiled Bindings to improve performance and ensure Type safety.

This Proposal extends the .Bind() extension method by providing the option of using TypedBinding which is the binding engine used by XAML Compiled Bindings to improve performance and ensure Type safety.

// One-way (aka read-only) Binding
new Label().Row(Row.Description).Bind(Label.TextProperty, (StoryModel m) => m.Description)

// Two-way Binding
new Entry().Bind(Entry.TextProperty, (SettingsViewModel vm) => vm.NumberOfTopStoriesToFetch, (SettingsViewModel vm, int text) => vm.NumberOfTopStoriesToFetch = text)

Motivation

The current implementation of .Bind() uses the Binding class which requires reflection when a change to the binding is applied.

This updated implementation brings the option of using TypedBinding with the .Bind() extension method which does not require reflection providing a substantial performance improvement for bindings.

Bindings TypedBinding
Uses Reflection Yes No
Type Safe No Yes

Detailed Design

A POC of this can be found on the Compiled-Bindings branch:

using Microsoft.Maui.Controls.Internals;

namespace CommunityToolkit.Maui.Markup;

/// <summary>
/// TypedBinding Extension Methods for Bindable Objects
/// </summary>
public static class TypedBindingExtensions
{
	/// <summary>Bind to a specified property</summary>
	public static TBindable Bind<TBindable, TBindingContext, TSource>(
		this TBindable bindable,
		BindableProperty targetProperty,
		Func<TBindingContext, TSource> getter,
		Action<TBindingContext, TSource>? setter = null,
		BindingMode? mode = null,
		string? stringFormat = null,
		TBindingContext? source = default) where TBindable : BindableObject
	{
		bindable.SetBinding(targetProperty, new TypedBinding<TBindingContext, TSource>(result => (getter(result), true), setter, null)
		{
			Mode = (setter, mode) switch
			{
				(_, not null) => mode.Value, // Always use the provided mode when given
				(null, null) => BindingMode.OneWay, // When setter is null, binding is read-only; use BindingMode.OneWay to improve performance
				_ => BindingMode.Default // Default to BindingMode.Default
			},
			StringFormat = stringFormat,
			Source = source,
		});

		return bindable;
	}

	/// <summary>Bind to a specified property with inline conversion</summary>
	public static TBindable Bind<TBindable, TBindingContext, TSource, TDest>(
		this TBindable bindable,
		BindableProperty targetProperty,
		Func<TBindingContext, TSource> getter,
		Action<TBindingContext, TSource>? setter = null,
		BindingMode? mode = null,
		Func<TSource?, TDest>? convert = null,
		Func<TDest?, TSource>? convertBack = null,
		string? stringFormat = null,
		TBindingContext? source = default,
		TDest? targetNullValue = default,
		TDest? fallbackValue = default) where TBindable : BindableObject
	{
		var converter = new FuncConverter<TSource, TDest, object>(convert, convertBack);
		bindable.SetBinding(targetProperty, new TypedBinding<TBindingContext, TSource>(result => (getter(result), true), setter, null)
		{
			Mode = (setter, mode) switch
			{
				(_, not null) => mode.Value, // Always use the provided mode when given
				(null, null) => BindingMode.OneWay, // When setter is null, binding is read-only; use BindingMode.OneWay to improve performance
				_ => BindingMode.Default // Default to BindingMode.Default
			},
			Converter = converter,
			StringFormat = stringFormat,
			Source = source,
			TargetNullValue = targetNullValue,
			FallbackValue = fallbackValue
		});

		return bindable;
	}

	/// <summary>Bind to a specified property with inline conversion and conversion parameter</summary>
	public static TBindable Bind<TBindable, TBindingContext, TSource, TParam, TDest>(
		this TBindable bindable,
		BindableProperty targetProperty,
		Func<TBindingContext, TSource> getter,
		Action<TBindingContext, TSource>? setter = null,
		BindingMode? mode = null,
		Func<TSource?, TParam?, TDest>? convert = null,
		Func<TDest?, TParam?, TSource>? convertBack = null,
		TParam? converterParameter = default,
		string? stringFormat = null,
		TBindingContext? source = default,
		TDest? targetNullValue = default,
		TDest? fallbackValue = default) where TBindable : BindableObject
	{
		var converter = new FuncConverter<TSource, TDest, TParam>(convert, convertBack);
		bindable.SetBinding(targetProperty, new TypedBinding<TBindingContext, TSource>(result => (getter(result), true), setter, null)
		{
			Mode = (setter, mode) switch
			{
				(_, not null) => mode.Value, // Always use the provided mode when given
				(null, null) => BindingMode.OneWay, // When setter is null, binding is read-only; use BindingMode.OneWay to improve performance
				_ => BindingMode.Default // Default to BindingMode.Default
			},
			Converter = converter,
			ConverterParameter = converterParameter,
			StringFormat = stringFormat,
			Source = source,
			TargetNullValue = targetNullValue,
			FallbackValue = fallbackValue
		});

		return bindable;
	}
}

Usage Syntax

// One-way (aka read-only) Binding
new Label().Row(Row.Description).Bind(Label.TextProperty, (StoryModel m) => m.Description)

// Two-way Binding
new Entry().Bind(Entry.TextProperty, (SettingsViewModel vm) => vm.NumberOfTopStoriesToFetch, (SettingsViewModel vm, int text) => vm.NumberOfTopStoriesToFetch = text)

Drawbacks

This is an overload to the existing .Bind() method, increasing the number of overloaded methods for .Bind() to 16.

This implementation also ignores TypedBinding’s string[] handler constructor parameter. This parameter isn’t documented and I’m unsure how it is being used and what use-cases it covers. However, I’m confident we can add support for this parameter in a future update without breaking changes.

Alternatives

TypedBinding can be used currently without C# Markup Extensions

// Two-way Binding
var entry = new Entry();
entry.SetBinding(Entry.TextProperty, new TypedBinding<SettingsViewModel, int>(vm => (vm.NumberOfTopStoriesToFetch, true), (vm, number) => vm.NumberOfTopStoriesToFetch = number, null));

Content = entry;

Unresolved Questions

Should we use a different name for this extension method, like .TypedBind()?

Issue Analytics

  • State:closed
  • Created 10 months ago
  • Reactions:8
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
bijingtoncommented, Feb 5, 2023

Oh I forgot to add in the proposal for the BindCommand extension! Let me add that now.

I personally prefer the explicitness of defining the property being bound rather than the defaults but I don’t feel too strongly about it.

When is the documentation planned to get updated on the official site here?

We believe we have updated all the relevant examples to use the new typed bindings. Once the next proposals are completed then we can update the rest of the docs. Unless you think we have missed some?

1reaction
brminnickcommented, Nov 23, 2022

instead of having BindingMode? why not use the Default

Good Question! I made BindingMode? mode = null default to null on purpose to slide in a small performance improvement.

Since TypedBinding can have a null setter, we can set the BindingMode to BindingMode.OneWay automatically for the user which improves performance over BindingMode.TwoWay.

Each .Bind() extension method in this proposal uses this logic to set the BindingMode of the binding:

Mode = (setter, mode) switch
{
  (_, not null) => mode.Value, // Always use the provided mode when given
  (null, null) => BindingMode.OneWay, // When setter is null, binding is read-only; use BindingMode.OneWay to improve performance
  _ => BindingMode.Default // Default to BindingMode.Default
},

^ If the user doesn’t provide a setter and doesn’t provide a BindingMode, then we can safely assume the binding is read-only and set the BindingMode to BindingMode.OneWay.

If the user does provide a setter, and doesn’t provide a BindingMode, then we’ll default to BindingMode.Default.

or make the OneWay as default (I believe it’s the default for regular bindings)?

We can’t make the default BindingMode.OneWay because not every binding defaults to BindingMode.OneWay.

https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/binding-mode?view=net-maui-7.0#two-way-bindings

Read more comments on GitHub >

github_iconTop Results From Across the Web

ECMAScript proposal: Type Annotations
This proposal aims to enable developers to add type annotations to their JavaScript code, allowing those annotations to be checked by a type...
Read more >
Binding Proposal Definition
Binding Proposal means a Proponent's detailed proposal in response to the RFP. Sample 1. Based on 1 documents. 1.
Read more >
Binding for Proposals: Presentation is Everything
Binding for Proposals: Presentation is Everything ; Plastic Coil Binding. Plastic Coil Binding · The document lays flat when open, and folds back ......
Read more >
Is A Proposal Legally Binding In Business?
The most common types of proposals include formal, informal, solicited, unsolicited or sole-source. Formal proposals usually involve a detailed document with ...
Read more >
25 Proposal Packaging / Binding ideas
Aug 18, 2016 - Explore Sales Engagement's board "Proposal Packaging / Binding" on Pinterest. See more ideas about book design, bookbinding, ...
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