[Trimming] Add new API to create TypedBindings from expression#19995
[Trimming] Add new API to create TypedBindings from expression#19995simonrozsival wants to merge 11 commits intodotnet:net9.0from
Conversation
jsuarezruiz
left a comment
There was a problem hiding this comment.
D:\a\_work\1\s\src\Compatibility\Core\src\Android\Renderers\ListViewAdapter.cs(739,5): error IL2026: Using member 'Microsoft.Maui.Controls.BindableObjectExtensions.SetBinding(BindableObject, BindableProperty, String, BindingMode, IValueConverter, String)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. The use of string-based binding is not trimming safe. Use the expression-based SetBinding overloads instead. [D:\a\_work\1\s\src\Compatibility\Core\src\Compatibility.csproj::TargetFramework=net9.0-android]
fe8e866 to
f5a5859
Compare
f5a5859 to
d3465b7
Compare
9c4799d to
3a929ed
Compare
|
/cc @jonathanpeppers this is ready for review now |
| if (bo is MenuItem) | ||
| { | ||
| imageBinding = TypedBindingFactory.Create(static (MenuItem item) => item.IconImageSource); | ||
| labelBinding = TypedBindingFactory.Create(static (MenuItem item) => item.Text); | ||
| } | ||
| else if (bo is BaseShellItem) | ||
| { | ||
| imageBinding = TypedBindingFactory.Create(static (BaseShellItem item) => item.FlyoutIcon); | ||
| labelBinding = TypedBindingFactory.Create(static (BaseShellItem item) => item.Title); | ||
| } | ||
| else | ||
| { | ||
| throw new InvalidOperationException("Invalid type for flyout item cell."); | ||
| } |
There was a problem hiding this comment.
Are there any other types that need to work here besides MenuItem and BaseShellItem? What was the exception you would have gotten previously for using the wrong type inside a flyout?
Or did previously you got no error, and the bindings just "didn't work"?
src/Controls/tests/Core.UnitTests/TypedBindingFromExpressionUnitTests.cs
Show resolved
Hide resolved
|
|
||
| namespace Microsoft.Maui.Controls | ||
| { | ||
| internal static class TypedBindingFactory |
There was a problem hiding this comment.
Looking at the code in here, it supports:
- ParameterExpression
- UnaryExpression
- MemberExpression
- MethodCallExpression
- BinaryExpression
Does this need to be exhaustive yet? Should we at least put a <summary> comment of which expressions would work?
It seems like we'd need to implement them all if this was able to be used by customers -- which, isn't the case yet.
There was a problem hiding this comment.
I think it's perfectly OK to support only a subset and throw if the provided expression is too complex. ASP.NET uses similar trick and also implements only a limited subset of the expression tree support. For example https://github.com/dotnet/aspnetcore/blob/c2a9c205aa65af4031e0fd6471c2aa89bde095c0/src/Mvc/Mvc.ViewFeatures/src/ExpressionMetadataProvider.cs#L72C59-L72C94.
There was a problem hiding this comment.
I focused only on mirroring what customers can do with binding paths - accessing members, arrays, and indexers (as described in https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/binding-path). From those 5 expression types, it also only supports a subset of them (for example unary operator - just Convert, AsType, method call - just special name methods get_Indexer, get_Item), but it's all that's necessary to support the expressions that are similar to the string paths in classic bindings. As @StephaneDelcroix told me, the subset of bindings I implemented is very close to one of the earlier expression-based APIs that Xamarin.Forms once had (https://github.com/xamarin/Xamarin.Forms/blob/5.0.0/Xamarin.Forms.Core/Binding.cs#L332).
You're definitely right that the documentation part is lacking. I'm assuming we'll need to update the official documentation if this feature goes in later, but I can definitely add that <summary> in this PR. Thanks for the suggestion.
| groupProxy.HeaderContent = _itemsView.CreateDefault(ListProxy.ProxiedEnumerable); | ||
| groupProxy.HeaderContent.BindingContext = groupProxy; | ||
| groupProxy.HeaderContent.SetBinding(TextCell.TextProperty, "Name"); | ||
| groupProxy.HeaderContent.SetBinding(TextCell.TextProperty, static (TemplatedItemsList<TView, TItem> list) => list.Name); |
There was a problem hiding this comment.
We are using BannedApiAnalyzers for a few things in this repo, do we need to also ban SetBinding(BindableProperty,string)? As the typed binding used here seems like it could be better for both performance and trimming/nativeaot.
There was a problem hiding this comment.
Yes, I think that would be a good thing to do, but maybe in a follow-up PR that removes the remaining usages of this API in the app (mostly Windows and Tizen specific code). In fact, I think we should ban using the Binding class altogether.
We should also add [RequiresUnreferencedCode] to the Binding class so that customers get trimming warnings if they use it in their apps.
There was a problem hiding this comment.
We should also add [RequiresUnreferencedCode] to the Binding class so that customers get trimming warnings if they use it in their apps.
Won't we have to do this - to "fix" all the warnings - anyway?
There was a problem hiding this comment.
@vitek-karas we don't need it to fix all the warnings we get when we build the dotnet new maui app. Binding internally uses BindingExpression which has trimming-unsafe code so we get a warning anyway. I think we should add it so that when customers build their apps where they use Binding, the warnings appear in their codebase and not in the MAUI assembly.
There was a problem hiding this comment.
I think we should add it so that when customers build their apps where they use Binding, the warnings appear in their codebase and not in the MAUI assembly.
That was exactly the reason I had in mind :-). Thanks.
|
Just for my understanding (I am still getting familiar with the context), regarding:
Would the changes in this PR enable us to improve XamlC codegen - covering more cases and making better use of TypedBindings as well, or it is purely to provide adequate API for using TypedBindings programatically? |
src/Controls/tests/Core.UnitTests/TypedBindingFromExpressionUnitTests.cs
Outdated
Show resolved
Hide resolved
Yes, we should be able to compile a lot more bindings into typed bindings. I'll update the description to make it clear. |
b2f2c8b to
7d7f60b
Compare
|
/rebase |
1 similar comment
|
/rebase |
938264d to
a1be504
Compare
|
Closing this prototype for now. We might revive it later. |
Description of Change
TypedBinding is a great replacement for the reflection-based
Bindingclass. Unfortunately, it can only be used through XamlC. This PR adds a new generic overload of theSetBindingextension method which will allow developers to use the typed binding from C#/F#.I recommend reviewing the PR commit-by-commit.
Note: This PR depends on #20415 (support for relative binding sources).
Expressions
In this PR, I'm introducing a way to replace Binding with TypedBinding through a new
SetBinding<TSource, TProperty>extension method. The extension method expects a getter expression instead of the string path. This has several advantages:The extension method parses the expression and it uses it to automatically generate the handlers array the same way XamlC generates it. In most cases, the getter expression can be used to generate the setter as well. The only case where this is not possible to compile the setter is when the last part of the setter is a custom indexer (we don't have access to the setter method). In this case, the developer needs to pass the setter Action manually.
The "compiled" getters and setters are almost as fast as explicit getters and setters would be. This change doesn't increase the size of the final app bundle too much either: the .ipa build using NativeAOT was roughly 1.25% bigger than with just the reflection-based bindings.
Performance implications
Based on running a few benchmarks (
BindingBenchmarker, modifiedTypedBindingBenchmarker), constructing TypedBindings from expressions is slightly slower than parsing the string path and the "compilation" also allocates more memory. I need to collect more data.TODO:
Follow-ups
new Binding("...")andSetBinding("...")in the MAUI codebase. These can be migrated in some follow-up PR. There's no need to do the migration right now in this PR (it's quite big already).- ControlGallery
- Controls.Sample
- Controls.Sample.UITests
- Windows specific code
- Tizen specific code
- Unit tests (Controls)
- Examples in XML docs
ShellSearchResultsRenderer(iOS) andShellSearchViewAdapter(Android) default template can't be migrated to typed binding and it won't work with typed binding. That's the (only) reason why I can't remove any trimming/AOT warnings in tests. We will need to disable it using a feature switch for native AOT in some follow-up PR.SetBinding<TSource, TProperty>method public + updatePublicAPI.Unshipped.txtPublicAPI.Unshipped.txtusing VSCode or manually for some reason.Issues Fixed
Contributes to #19397
Closes #19912