-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Describe the bug
Existing instructions on how to set up XAML code behind bindings have a few usability issues due to the new nullability compiler features being added.
Steps To Reproduce
Let's take a 101 binding example inspired from the RxUI docs (https://www.reactiveui.net/docs/handbook/data-binding/windows-presentation-foundation):
// Setup run button
this.OneWayBind(this.ViewModel, vm => vm.RunAllCommand, view => view.RunButton.Command)
.DisposeWith(disposable);
This should do the very simple job of binding a button's command to a viewmodel's command property.
Two problems come up with nullability.
- OneWayBind's
viewModel
parameter is not null, while ReactiveUserControl's ViewModel member is. This means the user has to add the!
operator to get things to pass in, which is undesirable:
// Setup run button
this.OneWayBind(this.ViewModel!, vm => vm.RunAllCommand, view => view.RunButton.Command)
.DisposeWith(disposable);
It's my understanding that !
should be avoided whenever possible, as it's bad coding habit that starts reintroducing routes for null ref errors into your code again.
- OneWayBind returns a
IReactiveBinding
object that is nullable. This doesn't hook intoDisposeWith
cleanly, as it expects a non null item. So now the user has to add?
checks after every bind call, which is not too clean:
// Setup run button
this.OneWayBind(this.ViewModel!, vm => vm.RunAllCommand, view => view.RunButton.Command)
?.DisposeWith(disposable);
Since bindings like this are written -all- the time when developing a GUI, I think it would be good to set up the structure to not require any !
usage, for sure, and then potentially try to avoid the need for elvis operators, if possible.
Expected behavior
I would expect this code without any extra operators to function:
// Setup run button
this.OneWayBind(this.ViewModel, vm => vm.RunAllCommand, view => view.RunButton.Command)
.DisposeWith(disposable);
For me, my suggested solutions would be to make the viewModel
parameter on OneWayBind nullable, so that it can take ReactiveControl's ViewModel property directly and cleanly. Since the bind definitions return potentially null bindings already, just have a null viewmodel short circuit and return a null binding, perhaps? Seems like the cleanest route.
public IReactiveBinding<TView, TViewModel, TVProp> OneWayBind<TViewModel, TView, TVMProp, TVProp>(
TViewModel? viewModel,
TView view,
...
Second, since the binding calls are returning potentially null objects, it might be nice to upgrade DisposeWith
to take nullable items, to remove the need for the ?
call between the binding and the dispose every time.
[return: NotNullIfNotNull("item")]
public static T? DisposeWith<T>(this T? item, CompositeDisposable compositeDisposable)
where T : class, IDisposable
{
if (compositeDisposable == null)
{
throw new ArgumentNullException(nameof(compositeDisposable));
}
if (item == null) return item;
compositeDisposable.Add(item);
return item;
}
Perhaps the new class constraint isn't desirable and can be avoided, but this general idea is what I'm thinking.