CommunityToolkit/Maui.Markup

[Proposal] Remove `BindableObject` Constraint from `.Assign()` and `.Invoke()`

brminnick opened this issue · 5 comments

Remove Constraints from Assign() and Invoke()

Summary

This Proposal removes the BindableObject constraint (where T : BindableObject) from .Assign() and .Invoke() extension methods.

Before

public static TBindable Assign<TBindable, TVariable>(this TBindable bindable, out TVariable variable) 
  where TBindable : BindableObject, TVariable;

public static TBindable Invoke<TBindable>(this TBindable bindable, Action<TBindable> action) 
  where TBindable : BindableObject;

After

public static TAssignable Assign<TAssignable, TVariable>(this TAssignable assignable, out TVariable variable) 
  where TAssignable : TVariable;

public static TAssignable Invoke<TAssignable>(this TAssignable assignable, Action<TAssignable> action);
  // no constraint

Motivation

There is nothing in Assign() or Invoke() that requires generic, T, to be a BindableObject.

Removing the constraint (eg removing where T : Bindable), allows the .Assign()and .Invoke() extension methods to be used on any type.

This is especially useful for custom controls that may implement BindableObject.

Detailed Design

I recommend creating a new class, public static class Object Extensions.

Both .Assign() and .Invoke() are currently in BindableObjectExtensions.cs. Keeping them in BindableObjectExtensions.cs doesn't make much sense now that they are no longer constrained to BindableObject.

namespace CommunityToolkit.Maui.Markup;

/// <summary>
/// Extension Methods for Unconstrained Objects
/// </summary>
public static class ObjectExtensions
{
	/// <summary>
	/// Assign <typeparam name="TAssignable"> to a variable
	/// </summary>
	/// <typeparam name="TAssignable"></typeparam>
	/// <typeparam name="TVariable"></typeparam>
	/// <param name="assignable"></param>
	/// <param name="variable"></param>
	/// <returns>TBindable</returns>
	public static TAssignable Assign<TAssignable, TVariable>(this TAssignable assignable, out TVariable variable)
		where TAssignable : TVariable
	{
		variable = assignable;
		return assignable;
	}

	/// <summary>
	/// Perform an action on <typeparam name="TAssignable">
	/// </summary>
	/// <typeparam name="TAssignable"></typeparam>
	/// <param name="assignable"></param>
	/// <param name="action"></param>
	/// <returns>TBindable</returns>
	public static TAssignable Invoke<TAssignable>(this TAssignable assignable, Action<TAssignable> action)
	{
		ArgumentNullException.ThrowIfNull(action);

		action.Invoke(assignable);
		return assignable;
	}
}

Usage Syntax

C# Usage

class SamplePage : ContentPage
{
  readonly Label _label;
  readonly string _title;
  
  public SamplePage()
  {
    Title = "Sample Page".Assign(out _title);
    Content = new Button().Invoke(button => button.Clicked += HandleButtonClicked);
  }
}

Drawbacks

None. This is not a breaking change.

Alternatives

We could create two new extension methods, but since it is not a breaking change to remove constraints, I recommend we move forward with keeping the existing extension methods and removing their constraints.

Unresolved Questions

None

I approve this feature ✅

Approve!

Gets my vote

I too approve ✅

Reopening Proposal.

Only Proposals moved to the Closed Project Column and Completed Project Column can be closed.