Repository: AvaloniaUI/Avalonia

Issue:
Regression in 11.1: MultiValueConverter returning BindingNotification throws InvalidCastException
### Describe the bug

In my application I have several `IMultiValueConverter`s that return a `BindingNotification` when values can't be converted. The [documentation](https://docs.avaloniaui.net/docs/guides/data-binding/how-to-create-a-custom-data-binding-converter#example) suggests this is a best practice.

With 11.0.10 I have no issues, however when I tried 11.1.0-rc1, I started to get `InvalidCastException`s like the following when navigating through the application:
<details>
  <summary><code>System.InvalidCastException: Unable to cast object of type 'MultiBindingBug.ViewModels.MainWindowViewModel' to type 'MultiBindingBug.ViewModels.MultiConverterViewModel'.</code></summary>

```
   at CompiledAvaloniaXaml.XamlIlHelpers.MultiBindingBug.ViewModels.MultiConverterViewModel,MultiBindingBug.Welcome!Getter(Object)
   at Avalonia.Data.Core.ClrPropertyInfo.Get(Object target)
   at Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.InpcPropertyAccessor.get_Value()
   at Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.InpcPropertyAccessor.SendCurrentValue()
```
</details>

In the debugger, stepping up through the stack, I see that in `PropertyInfoAccessorFactory.SendCurrentValue` a different exception is being caught:
<details>
  <summary><code>System.InvalidCastException: Unable to cast object of type 'Avalonia.Data.BindingNotification' to type 'System.String'.</code></summary>

```
   at Avalonia.PropertyStore.EffectiveValue`1.SetLocalValueAndRaise(ValueStore owner, AvaloniaProperty property, Object value)
   at Avalonia.PropertyStore.ValueStore.SetLocalValue(AvaloniaProperty property, Object value)
   at Avalonia.PropertyStore.ValueStore.Avalonia.Data.Core.IBindingExpressionSink.OnChanged(UntypedBindingExpressionBase instance, Boolean hasValueChanged, Boolean hasErrorChanged, Object value, BindingError error)
   at Avalonia.Data.Core.UntypedBindingExpressionBase.PublishValue(Object value, BindingError error)
   at Avalonia.Data.Core.UntypedObservableBindingExpression.System.IObserver<System.Object>.OnNext(Object value)
   at Avalonia.Reactive.Observable.<>c__DisplayClass4_1`1.<Where>b__1(TSource input)
   at Avalonia.Reactive.AnonymousObserver`1.OnNext(T value)
   at Avalonia.Reactive.Observable.<>c__DisplayClass2_1`2.<Select>b__1(TSource input)
   at Avalonia.Reactive.AnonymousObserver`1.OnNext(T value)
   at Avalonia.Reactive.Operators.Sink`1.ForwardOnNext(TTarget value)
   at Avalonia.Reactive.Operators.CombineLatest`2._.OnNext(Int32 index, TSource value)
   at Avalonia.Reactive.Operators.CombineLatest`2._.SourceObserver.OnNext(TSource value)
   at Avalonia.Reactive.LightweightObservableBase`1.PublishNext(T value)
   at Avalonia.Data.Core.UntypedBindingExpressionBase.ObservableSink.PublishNext(Object value)
   at Avalonia.Data.Core.UntypedBindingExpressionBase.ObservableSink.Avalonia.Data.Core.IBindingExpressionSink.OnChanged(UntypedBindingExpressionBase instance, Boolean hasValueChanged, Boolean hasErrorChanged, Object value, BindingError error)
   at Avalonia.Data.Core.UntypedBindingExpressionBase.PublishValue(Object value, BindingError error)
   at Avalonia.Data.Core.BindingExpression.ConvertAndPublishValue(Object value, BindingError error)
   at Avalonia.Data.Core.BindingExpression.OnNodeError(Int32 nodeIndex, String error)
   at Avalonia.Data.Core.BindingExpression.OnNodeValueChanged(Int32 nodeIndex, Object value, Exception dataValidationError)
   at Avalonia.Data.Core.ExpressionNodes.ExpressionNode.SetValue(Object value, Exception dataValidationError)
   at Avalonia.Data.Core.ExpressionNodes.ExpressionNode.SetValue(Object valueOrNotification)
   at Avalonia.Data.Core.ExpressionNodes.DataContextNode.OnPropertyChanged(Object sender, AvaloniaPropertyChangedEventArgs e)
   at Avalonia.AvaloniaObject.RaisePropertyChanged[T](AvaloniaProperty`1 property, Optional`1 oldValue, BindingValue`1 newValue, BindingPriority priority, Boolean isEffectiveValue)
   at Avalonia.PropertyStore.EffectiveValue`1.RaiseInheritedValueChanged(AvaloniaObject owner, AvaloniaProperty property, EffectiveValue oldValue, EffectiveValue newValue)
   at Avalonia.PropertyStore.ValueStore.InheritedValueChanged(AvaloniaProperty property, EffectiveValue oldValue, EffectiveValue newValue)
   at Avalonia.PropertyStore.ValueStore.InheritedValueChanged(AvaloniaProperty property, EffectiveValue oldValue, EffectiveValue newValue)
   at Avalonia.PropertyStore.ValueStore.InheritedValueChanged(AvaloniaProperty property, EffectiveValue oldValue, EffectiveValue newValue)
   at Avalonia.PropertyStore.ValueStore.InheritedValueChanged(AvaloniaProperty property, EffectiveValue oldValue, EffectiveValue newValue)
   at Avalonia.PropertyStore.ValueStore.SetInheritanceParent(AvaloniaObject newParent)
   at Avalonia.AvaloniaObject.set_InheritanceParent(AvaloniaObject value)
   at Avalonia.StyledElement.Avalonia.Controls.ISetLogicalParent.SetParent(ILogical parent)
   at Avalonia.StyledElement.ClearLogicalParent(IList children)
   at Avalonia.StyledElement.LogicalChildrenCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
   at Avalonia.Visual.LogicalChildrenCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
   at Avalonia.Collections.AvaloniaList`1.NotifyRemove(T item, Int32 index)
   at Avalonia.Collections.AvaloniaList`1.Remove(T item)
   at Avalonia.Controls.Presenters.ContentPresenter.UpdateChild(Object content)
   at Avalonia.Controls.Presenters.ContentPresenter.ContentChanged(AvaloniaPropertyChangedEventArgs e)
   at Avalonia.Controls.Presenters.ContentPresenter.OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
   at Avalonia.AvaloniaObject.OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)
   at Avalonia.Animation.Animatable.OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)
   at Avalonia.AvaloniaObject.RaisePropertyChanged[T](AvaloniaProperty`1 property, Optional`1 oldValue, BindingValue`1 newValue, BindingPriority priority, Boolean isEffectiveValue)
   at Avalonia.PropertyStore.EffectiveValue`1.SetAndRaiseCore(ValueStore owner, StyledProperty`1 property, T value, BindingPriority priority, Boolean isOverriddenCurrentValue, Boolean isCoercedDefaultValue)
   at Avalonia.PropertyStore.EffectiveValue`1.SetAndRaise(ValueStore owner, IValueEntry value, BindingPriority priority)
   at Avalonia.PropertyStore.ValueStore.ReevaluateEffectiveValue(AvaloniaProperty property, EffectiveValue current, IValueEntry changedValueEntry, Boolean ignoreLocalValue)
   at Avalonia.PropertyStore.ValueStore.Avalonia.Data.Core.IBindingExpressionSink.OnChanged(UntypedBindingExpressionBase instance, Boolean hasValueChanged, Boolean hasErrorChanged, Object value, BindingError error)
   at Avalonia.Data.Core.UntypedBindingExpressionBase.PublishValue(Object value, BindingError error)
   at Avalonia.Data.TemplateBinding.PublishValue()
   at Avalonia.Data.TemplateBinding.OnTemplatedParentPropertyChanged(Object sender, AvaloniaPropertyChangedEventArgs e)
   at Avalonia.AvaloniaObject.RaisePropertyChanged[T](AvaloniaProperty`1 property, Optional`1 oldValue, BindingValue`1 newValue, BindingPriority priority, Boolean isEffectiveValue)
   at Avalonia.PropertyStore.EffectiveValue`1.SetAndRaiseCore(ValueStore owner, StyledProperty`1 property, T value, BindingPriority priority, Boolean isOverriddenCurrentValue, Boolean isCoercedDefaultValue)
   at Avalonia.PropertyStore.EffectiveValue`1.SetLocalValueAndRaise(ValueStore owner, StyledProperty`1 property, T value)
   at Avalonia.PropertyStore.EffectiveValue`1.SetLocalValueAndRaise(ValueStore owner, AvaloniaProperty property, Object value)
   at Avalonia.PropertyStore.ValueStore.SetLocalValue(AvaloniaProperty property, Object value)
   at Avalonia.PropertyStore.ValueStore.Avalonia.Data.Core.IBindingExpressionSink.OnChanged(UntypedBindingExpressionBase instance, Boolean hasValueChanged, Boolean hasErrorChanged, Object value, BindingError error)
   at Avalonia.Data.Core.UntypedBindingExpressionBase.PublishValue(Object value, BindingError error)
   at Avalonia.Data.Core.BindingExpression.ConvertAndPublishValue(Object value, BindingError error)
   at Avalonia.Data.Core.BindingExpression.OnNodeValueChanged(Int32 nodeIndex, Object value, Exception dataValidationError)
   at Avalonia.Data.Core.ExpressionNodes.ExpressionNode.SetValue(Object value, Exception dataValidationError)
   at Avalonia.Data.Core.ExpressionNodes.ExpressionNode.SetValue(Object valueOrNotification)
   at Avalonia.Data.Core.ExpressionNodes.PropertyAccessorNode.OnValueChanged(Object newValue)
   at Avalonia.Data.Core.Plugins.PropertyAccessorBase.PublishValue(Object value)
   at Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.InpcPropertyAccessor.SendCurrentValue()
```
</details>

This is what lead me to the conclusion that the problem was with the converter returning a `BindingNotification`.

### To Reproduce

In my application, the exception occurs when navigating between views and seems to be triggered by changes in DataContext. I have tried to create a minimal repro:

<details>
  <summary>MainWindow.xaml</summary>

```xaml
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:MultiBindingBug.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:views="clr-namespace:MultiBindingBug.Views"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="MultiBindingBug.Views.MainWindow"
        x:DataType="vm:MainWindowViewModel"
        Title="MultiBindingBug">
    <Grid ColumnDefinitions="Auto,*">
        <StackPanel Grid.Column="0">
            <Button Command="{Binding HomeCommand}">Home</Button>
            <Button Command="{Binding MultiConverterCommand}">MultiConverter</Button>
        </StackPanel>

        <ContentControl Content="{Binding SelectedContent}"
                        Margin="0,0,2,0"
                        Grid.Column="1">
            <ContentControl.DataTemplates>
                <DataTemplate DataType="vm:HomeViewModel">
                    <views:HomeView />
                </DataTemplate>
                <DataTemplate DataType="vm:MultiConverterViewModel">
                    <views:MultiConverterView />
                </DataTemplate>
            </ContentControl.DataTemplates>
        </ContentControl>
    </Grid>
</Window>
```
</details>

<details>
  <summary>HomeView.xaml</summary>

```xaml
<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
             x:Class="MultiBindingBug.Views.HomeView">
    Home
</UserControl>
```
</details>

<details>
  <summary>MultiConverter.xaml</summary>

```xaml
<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:vm="using:MultiBindingBug.ViewModels"
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
             x:Class="MultiBindingBug.Views.MultiConverterView"
             x:DataType="vm:MultiConverterViewModel">
    <StackPanel>
        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock.Text>
                <MultiBinding Converter="{x:Static vm:MultiValueConverter.Instance}" ConverterParameter=" ">
                    <Binding Path="Welcome" />
                    <Binding Path="To" />
                    <Binding Path="Avalonia" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </StackPanel>
</UserControl>
```
</details>

<details>
  <summary>MainWindowViewModel.cs</summary>

```cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace MultiBindingBug.ViewModels;

public partial class MainWindowViewModel : ObservableObject
{
    private readonly HomeViewModel _home = new HomeViewModel();
    private readonly MultiConverterViewModel _multiConverter = new MultiConverterViewModel();

    public MainWindowViewModel() => SelectedContent = _home;

    [RelayCommand] public void Home() => SelectedContent = _home;
    [RelayCommand] public void MultiConverter() => SelectedContent = _multiConverter;

    [ObservableProperty] private object? _selectedContent;
}
```
</details>

<details>
  <summary>HomeViewModel.cs</summary>

```cs
namespace MultiBindingBug.ViewModels;

public class HomeViewModel
{
}
```
</details>

<details>
  <summary>MultiConverterViewModel.cs</summary>

```cs
namespace MultiBindingBug.ViewModels;

public class MultiConverterViewModel
{
    public string Welcome => "Welcome";
    public string To => "to";
    public string Avalonia => "Avalonia!";
}
```
</details>

<details>
  <summary>MultiValueConverter.cs</summary>

```cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Data;
using Avalonia.Data.Converters;

namespace MultiBindingBug.ViewModels;

public class MultiValueConverter : IMultiValueConverter
{
    public static MultiValueConverter Instance { get; } = new();

    public object Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
    {
        if (values.All(v => v is string) && parameter is string separator)
        {
            return string.Join(separator, values.Cast<string>());
        }
        
        return new BindingNotification(new ArgumentException(), BindingErrorType.Error);
    }
}
```
</details>

Steps to reproduce:
1. Run the application. `HomeView` is displayed.
2. Click on the "MultiConverter" button to switch to `MultiConverterView`.
3. Click on the "Home" button. The exception is thrown.

### Expected behavior

No exception is thrown. (If I'm doing something wrong in my `MultiValueConverter` please advise.)

### Avalonia version

11.1.0-rc1

### OS

Windows

### Additional context

Workarounds:
1. Downgrade to Avalonia 11.0.10
2. Modify `MultiValueConverter.cs` to return `null` instead of a `BindingNotification`


Hints:
Definitely looks like a regression introduced by the binding system refactor, will investigate.

Not sure if this is a showstopper and needs to be fixed for 11.1.0 or whether it can wait for 11.1.1 though.
> Definitely looks like a regression introduced by the binding system refactor, will investigate.
> 
> Not sure if this is a showstopper and needs to be fixed for 11.0.0 or whether it can wait for 11.0.1 though.

Assuming you mean 11.1.0 and 11.1.1?

For me, I don't use the `BindingNotification`s my converters were returning, so I could return null. I'm also not in a big hurry to move to 11.1, so I could wait. I would suggest documenting this as a known issue though, because my application was very broken and it took me a long time to track it down to the `BindingNotifcation`s.
> Assuming you mean 11.1.0 and 11.1.1?

Yeah sorry, edited. I'll take a look and see how hard the fix is before we decide if it'll be in 11.1.0. It definitely feels like less of a showstopper than #16071 though.

Patch:
diff --git a/src/Avalonia.Base/Data/Core/UntypedObservableBindingExpression.cs b/src/Avalonia.Base/Data/Core/UntypedObservableBindingExpression.cs
index 1e26caa0512..df529d8675e 100644
--- a/src/Avalonia.Base/Data/Core/UntypedObservableBindingExpression.cs
+++ b/src/Avalonia.Base/Data/Core/UntypedObservableBindingExpression.cs
@@ -30,5 +30,18 @@ protected override void StopCore()
 
     void IObserver<object?>.OnCompleted() { }
     void IObserver<object?>.OnError(Exception error) { }
-    void IObserver<object?>.OnNext(object? value) => PublishValue(value);
+    
+    void IObserver<object?>.OnNext(object? value)
+    {
+        if (value is BindingNotification n)
+        {
+            var v = n.Value;
+            var e = n.Error is not null ? new BindingError(n.Error, n.ErrorType) : null;
+            PublishValue(v, e);
+        }
+        else
+        {
+            PublishValue(value);
+        }
+    }
 }


Test patch:
diff --git a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs
index f6a4437a9ef..0a20c25b15f 100644
--- a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs
+++ b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs
@@ -180,6 +180,28 @@ public void MultiBinding_Without_StringFormat_And_Converter()
             Assert.Equal(target.ItemsView[2], source.C);
         }
 
+        [Fact]
+        public void Converter_Can_Return_BindingNotification()
+        {
+            var source = new { A = 1, B = 2, C = 3 };
+            var target = new TextBlock { DataContext = source };
+
+            var binding = new MultiBinding
+            {
+                Converter = new BindingNotificationConverter(),
+                Bindings = new[]
+                {
+                    new Binding { Path = "A" },
+                    new Binding { Path = "B" },
+                    new Binding { Path = "C" },
+                },
+            };
+
+            target.Bind(TextBlock.TextProperty, binding);
+
+            Assert.Equal("1,2,3-BindingNotification", target.Text);
+        }
+
         private class ConcatConverter : IMultiValueConverter
         {
             public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
@@ -203,5 +225,16 @@ public object Convert(IList<object> values, Type targetType, object parameter, C
                 return null;
             }
         }
+
+        private class BindingNotificationConverter : IMultiValueConverter
+        {
+            public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
+            {
+                return new BindingNotification(
+                    new ArgumentException(),
+                    BindingErrorType.Error,
+                    string.Join(",", values) + "-BindingNotification");
+            }
+        }
     }
 }