diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 5acbae35d8..72a3fbe925 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -18,7 +18,6 @@ namespace Avalonia public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged { private AvaloniaObject? _inheritanceParent; - private List? _directBindings; private PropertyChangedEventHandler? _inpcChanged; private EventHandler? _propertyChanged; private List? _inheritanceChildren; @@ -336,6 +335,7 @@ namespace Avalonia property = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); + property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property); LogPropertySet(property, value, BindingPriority.LocalValue); SetDirectValueUnchecked(property, value); } @@ -343,7 +343,6 @@ namespace Avalonia /// /// Binds a to an observable. /// - /// The type of the property. /// The property. /// The observable. /// The priority of the binding. @@ -355,7 +354,6 @@ namespace Avalonia IObservable source, BindingPriority priority = BindingPriority.LocalValue) => property.RouteBind(this, source, priority); - /// /// Binds a to an observable. /// @@ -433,10 +431,9 @@ namespace Avalonia /// public IDisposable Bind( DirectPropertyBase property, - IObservable> source) + IObservable source) { property = property ?? throw new ArgumentNullException(nameof(property)); - source = source ?? throw new ArgumentNullException(nameof(source)); VerifyAccess(); property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property); @@ -446,15 +443,59 @@ namespace Avalonia throw new ArgumentException($"The property {property.Name} is readonly."); } - Logger.TryGet(LogEventLevel.Verbose, LogArea.Property)?.Log( - this, - "Bound {Property} to {Binding} with priority LocalValue", - property, - GetDescription(source)); + return _values.AddBinding(property, source); + } + + /// + /// Binds a to an observable. + /// + /// The type of the property. + /// The property. + /// The observable. + /// + /// A disposable which can be used to terminate the binding. + /// + public IDisposable Bind( + DirectPropertyBase property, + IObservable source) + { + property = property ?? throw new ArgumentNullException(nameof(property)); + VerifyAccess(); + + property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property); - _directBindings ??= new List(); + if (property.IsReadOnly) + { + throw new ArgumentException($"The property {property.Name} is readonly."); + } - return new DirectBindingSubscription(this, property, source); + return _values.AddBinding(property, source); + } + + /// + /// Binds a to an observable. + /// + /// The type of the property. + /// The property. + /// The observable. + /// + /// A disposable which can be used to terminate the binding. + /// + public IDisposable Bind( + DirectPropertyBase property, + IObservable> source) + { + property = property ?? throw new ArgumentNullException(nameof(property)); + VerifyAccess(); + + property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property); + + if (property.IsReadOnly) + { + throw new ArgumentException($"The property {property.Name} is readonly."); + } + + return _values.AddBinding(property, source); } /// @@ -562,7 +603,7 @@ namespace Avalonia { } - // + /// /// Raises the event for a direct property. /// /// The property that has changed. @@ -644,17 +685,15 @@ namespace Avalonia /// /// The property. /// The value. - private void SetDirectValueUnchecked(DirectPropertyBase property, T value) + internal void SetDirectValueUnchecked(DirectPropertyBase property, T value) { - var p = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property); - if (value is UnsetValueType) { - p.InvokeSetter(this, p.GetUnsetValue(GetType())); + property.InvokeSetter(this, property.GetUnsetValue(GetType())); } else if (!(value is DoNothingType)) { - p.InvokeSetter(this, value); + property.InvokeSetter(this, value); } } @@ -663,15 +702,8 @@ namespace Avalonia /// /// The property. /// The value. - private void SetDirectValueUnchecked(DirectPropertyBase property, BindingValue value) + internal void SetDirectValueUnchecked(DirectPropertyBase property, BindingValue value) { - var p = AvaloniaPropertyRegistry.Instance.FindRegisteredDirect(this, property); - - if (p == null) - { - throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}"); - } - LoggingUtils.LogIfNecessary(this, property, value); switch (value.Type) @@ -691,7 +723,7 @@ namespace Avalonia break; } - var metadata = p.GetMetadata(GetType()); + var metadata = property.GetMetadata(GetType()); if (metadata.EnableDataValidation == true) { @@ -725,50 +757,5 @@ namespace Avalonia value, priority); } - - private class DirectBindingSubscription : IObserver>, IDisposable - { - private readonly AvaloniaObject _owner; - private readonly DirectPropertyBase _property; - private readonly IDisposable _subscription; - - public DirectBindingSubscription( - AvaloniaObject owner, - DirectPropertyBase property, - IObservable> source) - { - _owner = owner; - _property = property; - _owner._directBindings!.Add(this); - _subscription = source.Subscribe(this); - } - - public void Dispose() - { - // _subscription can be null, if Subscribe failed with an exception. - _subscription?.Dispose(); - _owner._directBindings!.Remove(this); - } - - public void OnCompleted() => Dispose(); - public void OnError(Exception error) => Dispose(); - public void OnNext(BindingValue value) - { - if (Dispatcher.UIThread.CheckAccess()) - { - _owner.SetDirectValueUnchecked(_property, value); - } - else - { - // To avoid allocating closure in the outer scope we need to capture variables - // locally. This allows us to skip most of the allocations when on UI thread. - var instance = _owner; - var property = _property; - var newValue = value; - - Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue)); - } - } - } } } diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 4ad47ce8c8..71cf5e4a70 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -261,7 +261,6 @@ namespace Avalonia } throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); - } /// @@ -280,14 +279,17 @@ namespace Avalonia IObservable source, BindingPriority priority = BindingPriority.LocalValue) { - target = target ?? throw new ArgumentNullException(nameof(target)); - property = property ?? throw new ArgumentNullException(nameof(property)); - source = source ?? throw new ArgumentNullException(nameof(source)); + if (target is AvaloniaObject ao) + { + return property switch + { + StyledPropertyBase styled => ao.Bind(styled, source, priority), + DirectPropertyBase direct => ao.Bind(direct, source), + _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), + }; + } - return target.Bind( - property, - source.ToBindingValue(), - priority); + throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); } /// diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 304325c791..754142bdad 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -505,17 +505,6 @@ namespace Avalonia IObservable source, BindingPriority priority); - /// - /// Routes an untyped Bind call to a typed call. - /// - /// The object instance. - /// The binding source. - /// The priority. - internal abstract IDisposable RouteBind( - AvaloniaObject o, - IObservable> source, - BindingPriority priority); - /// /// Overrides the metadata for the property on the specified type. /// diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs index 4b97b1a02c..b67056e554 100644 --- a/src/Avalonia.Base/Data/BindingNotification.cs +++ b/src/Avalonia.Base/Data/BindingNotification.cs @@ -241,26 +241,6 @@ namespace Avalonia.Data _value = value; } - public BindingValue ToBindingValue() - { - if (ErrorType == BindingErrorType.None) - { - return HasValue ? new BindingValue(Value) : BindingValue.Unset; - } - else if (ErrorType == BindingErrorType.Error) - { - return BindingValue.BindingError( - Error!, - HasValue ? new Optional(Value) : Optional.Empty); - } - else - { - return BindingValue.DataValidationError( - Error!, - HasValue ? new Optional(Value) : Optional.Empty); - } - } - /// public override string ToString() { diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index 247938dec4..595295bf36 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/src/Avalonia.Base/Data/BindingValue.cs @@ -231,19 +231,64 @@ namespace Avalonia.Data /// /// Creates a from an object, handling the special values - /// and . + /// , and + /// . /// /// The untyped value. /// The typed binding value. public static BindingValue FromUntyped(object? value) { - return value switch + if (value == AvaloniaProperty.UnsetValue) + return Unset; + else if (value == BindingOperations.DoNothing) + return DoNothing; + + var type = BindingValueType.Value; + T? v = default; + Exception? error = null; + List? errors = null; + + if (value is BindingNotification n) { - UnsetValueType _ => Unset, - DoNothingType _ => DoNothing, - BindingNotification n => n.ToBindingValue().Cast(), - _ => new BindingValue((T)value!) - }; + error = n.Error; + type = n.ErrorType switch + { + BindingErrorType.Error => BindingValueType.BindingError, + BindingErrorType.DataValidationError => BindingValueType.DataValidationError, + _ => BindingValueType.Value, + }; + + if (n.HasValue) + type |= BindingValueType.HasValue; + value = n.Value; + } + + if ((type & BindingValueType.HasValue) != 0) + { + if (TypeUtilities.TryConvertImplicit(typeof(T), value, out var typed)) + v = (T)typed!; + else + { + var e = new InvalidCastException( + $"Unable to convert object '{value ?? "(null)"}' " + + $"of type '{value?.GetType()}' to type '{typeof(T)}'."); + + if (error is null) + error = e; + else + { + errors ??= new List() { error }; + errors.Add(e); + } + + type = BindingValueType.BindingError; + } + } + + if (errors is not null) + error = new AggregateException(errors); + + return new BindingValue(type, v, error); } public static bool operator !=(BindingValue x, Optional y) @@ -401,61 +446,4 @@ namespace Avalonia.Data } } } - - public static class BindingValueExtensions - { - /// - /// Casts the type of a using only the C# cast operator. - /// - /// The target type. - /// The binding value. - /// The cast value. - public static BindingValue Cast(this BindingValue value) - { - return value.Type switch - { - BindingValueType.DoNothing => BindingValue.DoNothing, - BindingValueType.UnsetValue => BindingValue.Unset, - BindingValueType.Value => new BindingValue((T)value.Value!), - BindingValueType.BindingError => BindingValue.BindingError(value.Error!), - BindingValueType.BindingErrorWithFallback => BindingValue.BindingError( - value.Error!, - (T)value.Value!), - BindingValueType.DataValidationError => BindingValue.DataValidationError(value.Error!), - BindingValueType.DataValidationErrorWithFallback => BindingValue.DataValidationError( - value.Error!, - (T)value.Value!), - _ => throw new NotSupportedException("Invalid BindingValue type."), - }; - } - - /// - /// Casts the type of a using the implicit conversions - /// allowed by the C# language. - /// - /// The target type. - /// The binding value. - /// The cast value. - /// - /// Note that this method uses reflection and as such may be slow. - /// - public static BindingValue Convert(this BindingValue value) - { - return value.Type switch - { - BindingValueType.DoNothing => BindingValue.DoNothing, - BindingValueType.UnsetValue => BindingValue.Unset, - BindingValueType.Value => new BindingValue(TypeUtilities.ConvertImplicit(value.Value!)), - BindingValueType.BindingError => BindingValue.BindingError(value.Error!), - BindingValueType.BindingErrorWithFallback => BindingValue.BindingError( - value.Error!, - TypeUtilities.ConvertImplicit(value.Value!)), - BindingValueType.DataValidationError => BindingValue.DataValidationError(value.Error!), - BindingValueType.DataValidationErrorWithFallback => BindingValue.DataValidationError( - value.Error!, - TypeUtilities.ConvertImplicit(value.Value!)), - _ => throw new NotSupportedException("Invalid BindingValue type."), - }; - } - } } diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index fcb78a9b42..447e2425e6 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -178,19 +178,7 @@ namespace Avalonia IObservable source, BindingPriority priority) { - // TODO: this requires a double adapter, we should make AvaloniaObject - // accept an `IObservable` for direct properties directly. - return RouteBind(o, source.ToBindingValue(), priority); - } - - /// - internal override IDisposable RouteBind( - AvaloniaObject o, - IObservable> source, - BindingPriority priority) - { - var adapter = TypedBindingAdapter.Create(o, this, source); - return o.Bind(this, adapter); + return o.Bind(this, source); } } } diff --git a/src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs b/src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs new file mode 100644 index 0000000000..cbe2435953 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs @@ -0,0 +1,76 @@ +using System; +using Avalonia.Data; +using Avalonia.Threading; + +namespace Avalonia.PropertyStore +{ + internal class DirectBindingObserver : IObserver, + IObserver>, + IDisposable + { + private readonly ValueStore _owner; + private IDisposable? _subscription; + + public DirectBindingObserver(ValueStore owner, DirectPropertyBase property) + { + _owner = owner; + Property = property; + } + + public DirectPropertyBase Property { get;} + + public void Start(IObservable source) + { + _subscription = source.Subscribe(this); + } + + public void Start(IObservable> source) + { + _subscription = source.Subscribe(this); + } + + public void Dispose() + { + _subscription?.Dispose(); + _subscription = null; + _owner.OnLocalValueBindingCompleted(Property, this); + } + + public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); + public void OnError(Exception error) => OnCompleted(); + + public void OnNext(T value) + { + if (Dispatcher.UIThread.CheckAccess()) + { + _owner.Owner.SetDirectValueUnchecked(Property, value); + } + else + { + // To avoid allocating closure in the outer scope we need to capture variables + // locally. This allows us to skip most of the allocations when on UI thread. + var instance = _owner.Owner; + var property = Property; + var newValue = value; + Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue)); + } + } + + public void OnNext(BindingValue value) + { + if (Dispatcher.UIThread.CheckAccess()) + { + _owner.Owner.SetDirectValueUnchecked(Property, value); + } + else + { + // To avoid allocating closure in the outer scope we need to capture variables + // locally. This allows us to skip most of the allocations when on UI thread. + var instance = _owner.Owner; + var property = Property; + var newValue = value; + Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue)); + } + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs new file mode 100644 index 0000000000..3d3b1a3e4d --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs @@ -0,0 +1,55 @@ +using System; +using Avalonia.Data; +using Avalonia.Threading; + +namespace Avalonia.PropertyStore +{ + internal class DirectUntypedBindingObserver : IObserver, + IDisposable + { + private readonly ValueStore _owner; + private IDisposable? _subscription; + + public DirectUntypedBindingObserver(ValueStore owner, DirectPropertyBase property) + { + _owner = owner; + Property = property; + } + + public DirectPropertyBase Property { get;} + + public void Start(IObservable source) + { + _subscription = source.Subscribe(this); + } + + public void Dispose() + { + _subscription?.Dispose(); + _subscription = null; + _owner.OnLocalValueBindingCompleted(Property, this); + } + + public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); + public void OnError(Exception error) => OnCompleted(); + + public void OnNext(object? value) + { + var typed = BindingValue.FromUntyped(value); + + if (Dispatcher.UIThread.CheckAccess()) + { + _owner.Owner.SetDirectValueUnchecked(Property, typed); + } + else + { + // To avoid allocating closure in the outer scope we need to capture variables + // locally. This allows us to skip most of the allocations when on UI thread. + var instance = _owner.Owner; + var property = Property; + var newValue = value; + Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, typed)); + } + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 41f7b1711b..8d6a1ae6c9 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -120,6 +120,36 @@ namespace Avalonia.PropertyStore } } + public IDisposable AddBinding(DirectPropertyBase property, IObservable> source) + { + var observer = new DirectBindingObserver(this, property); + DisposeExistingLocalValueBinding(property); + _localValueBindings ??= new(); + _localValueBindings[property.Id] = observer; + observer.Start(source); + return observer; + } + + public IDisposable AddBinding(DirectPropertyBase property, IObservable source) + { + var observer = new DirectBindingObserver(this, property); + DisposeExistingLocalValueBinding(property); + _localValueBindings ??= new(); + _localValueBindings[property.Id] = observer; + observer.Start(source); + return observer; + } + + public IDisposable AddBinding(DirectPropertyBase property, IObservable source) + { + var observer = new DirectUntypedBindingObserver(this, property); + DisposeExistingLocalValueBinding(property); + _localValueBindings ??= new(); + _localValueBindings[property.Id] = observer; + observer.Start(source); + return observer; + } + public void ClearLocalValue(AvaloniaProperty property) { if (TryGetEffectiveValue(property, out var effective) && @@ -470,7 +500,8 @@ namespace Avalonia.PropertyStore } /// - /// Called when a completes. + /// Called when a or + /// completes. /// /// The previously bound property. /// The observer. @@ -641,7 +672,6 @@ namespace Avalonia.PropertyStore /// Adds a new effective value, raises the initial /// event and notifies inheritance children if necessary . /// - /// The property type. /// The property. /// The property value. /// The value priority. diff --git a/src/Avalonia.Base/Reactive/BindingValueAdapter.cs b/src/Avalonia.Base/Reactive/BindingValueAdapter.cs deleted file mode 100644 index 8eaf21dcdc..0000000000 --- a/src/Avalonia.Base/Reactive/BindingValueAdapter.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Reactive.Subjects; -using Avalonia.Data; - -namespace Avalonia.Reactive -{ - internal class BindingValueAdapter : SingleSubscriberObservableBase>, - IObserver - { - private readonly IObservable _source; - private IDisposable? _subscription; - - public BindingValueAdapter(IObservable source) => _source = source; - public void OnCompleted() => PublishCompleted(); - public void OnError(Exception error) => PublishError(error); - public void OnNext(T value) => PublishNext(BindingValue.FromUntyped(value)); - protected override void Subscribed() => _subscription = _source.Subscribe(this); - protected override void Unsubscribed() => _subscription?.Dispose(); - } - - internal class BindingValueSubjectAdapter : SingleSubscriberObservableBase>, - ISubject> - { - private readonly ISubject _source; - private readonly Inner _inner; - private IDisposable? _subscription; - - public BindingValueSubjectAdapter(ISubject source) - { - _source = source; - _inner = new Inner(this); - } - - public void OnCompleted() => _source.OnCompleted(); - public void OnError(Exception error) => _source.OnError(error); - - public void OnNext(BindingValue value) - { - if (value.HasValue) - { - _source.OnNext(value.Value); - } - } - - protected override void Subscribed() => _subscription = _source.Subscribe(_inner); - protected override void Unsubscribed() => _subscription?.Dispose(); - - private class Inner : IObserver - { - private readonly BindingValueSubjectAdapter _owner; - - public Inner(BindingValueSubjectAdapter owner) => _owner = owner; - - public void OnCompleted() => _owner.PublishCompleted(); - public void OnError(Exception error) => _owner.PublishError(error); - public void OnNext(T value) => _owner.PublishNext(BindingValue.FromUntyped(value)); - } - } -} diff --git a/src/Avalonia.Base/Reactive/BindingValueExtensions.cs b/src/Avalonia.Base/Reactive/BindingValueExtensions.cs deleted file mode 100644 index 770da2cab4..0000000000 --- a/src/Avalonia.Base/Reactive/BindingValueExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Reactive.Subjects; -using Avalonia.Data; - -namespace Avalonia.Reactive -{ - public static class BindingValueExtensions - { - public static IObservable> ToBindingValue(this IObservable source) - { - source = source ?? throw new ArgumentNullException(nameof(source)); - return new BindingValueAdapter(source); - } - - public static ISubject> ToBindingValue(this ISubject source) - { - source = source ?? throw new ArgumentNullException(nameof(source)); - return new BindingValueSubjectAdapter(source); - } - - public static IObservable ToUntyped(this IObservable> source) - { - source = source ?? throw new ArgumentNullException(nameof(source)); - return new UntypedBindingAdapter(source); - } - - public static ISubject ToUntyped(this ISubject> source) - { - source = source ?? throw new ArgumentNullException(nameof(source)); - return new UntypedBindingSubjectAdapter(source); - } - } -} diff --git a/src/Avalonia.Base/Reactive/TypedBindingAdapter.cs b/src/Avalonia.Base/Reactive/TypedBindingAdapter.cs deleted file mode 100644 index f75917a00e..0000000000 --- a/src/Avalonia.Base/Reactive/TypedBindingAdapter.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using Avalonia.Data; -using Avalonia.Logging; - -namespace Avalonia.Reactive -{ - internal class TypedBindingAdapter : SingleSubscriberObservableBase>, - IObserver> - { - private readonly IAvaloniaObject _target; - private readonly AvaloniaProperty _property; - private readonly IObservable> _source; - private IDisposable? _subscription; - - public TypedBindingAdapter( - IAvaloniaObject target, - AvaloniaProperty property, - IObservable> source) - { - _target = target; - _property = property; - _source = source; - } - - public void OnNext(BindingValue value) - { - try - { - PublishNext(value.Convert()); - } - catch (InvalidCastException e) - { - var unwrappedValue = value.HasValue ? value.Value : null; - - Logger.TryGet(LogEventLevel.Error, LogArea.Binding)?.Log( - _target, - "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", - _property.Name, - _property.PropertyType, - unwrappedValue, - unwrappedValue?.GetType()); - PublishNext(BindingValue.BindingError(e)); - } - } - - public void OnCompleted() => PublishCompleted(); - public void OnError(Exception error) => PublishError(error); - - public static IObservable> Create( - IAvaloniaObject target, - AvaloniaProperty property, - IObservable> source) - { - return source is IObservable> result ? - result : - new TypedBindingAdapter(target, property, source); - } - - protected override void Subscribed() => _subscription = _source.Subscribe(this); - protected override void Unsubscribed() => _subscription?.Dispose(); - } -} diff --git a/src/Avalonia.Base/Reactive/UntypedBindingAdapter.cs b/src/Avalonia.Base/Reactive/UntypedBindingAdapter.cs deleted file mode 100644 index 58fbee7b8f..0000000000 --- a/src/Avalonia.Base/Reactive/UntypedBindingAdapter.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Reactive.Subjects; -using Avalonia.Data; - -namespace Avalonia.Reactive -{ - internal class UntypedBindingAdapter : SingleSubscriberObservableBase, - IObserver> - { - private readonly IObservable> _source; - private IDisposable? _subscription; - - public UntypedBindingAdapter(IObservable> source) => _source = source; - public void OnCompleted() => PublishCompleted(); - public void OnError(Exception error) => PublishError(error); - public void OnNext(BindingValue value) => value.ToUntyped(); - protected override void Subscribed() => _subscription = _source.Subscribe(this); - protected override void Unsubscribed() => _subscription?.Dispose(); - } - - internal class UntypedBindingSubjectAdapter : SingleSubscriberObservableBase, - ISubject - { - private readonly ISubject> _source; - private readonly Inner _inner; - private IDisposable? _subscription; - - public UntypedBindingSubjectAdapter(ISubject> source) - { - _source = source; - _inner = new Inner(this); - } - - public void OnCompleted() => _source.OnCompleted(); - public void OnError(Exception error) => _source.OnError(error); - public void OnNext(object? value) - { - _source.OnNext(BindingValue.FromUntyped(value)); - } - - protected override void Subscribed() => _subscription = _source.Subscribe(_inner); - protected override void Unsubscribed() => _subscription?.Dispose(); - - private class Inner : IObserver> - { - private readonly UntypedBindingSubjectAdapter _owner; - - public Inner(UntypedBindingSubjectAdapter owner) => _owner = owner; - - public void OnCompleted() => _owner.PublishCompleted(); - public void OnError(Exception error) => _owner.PublishError(error); - public void OnNext(BindingValue value) => _owner.PublishNext(value.ToUntyped()); - } - } -} diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index 2dcd468f15..90b724bee6 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -239,16 +239,6 @@ namespace Avalonia return target.Bind(this, source, priority); } - /// - internal override IDisposable RouteBind( - AvaloniaObject o, - IObservable> source, - BindingPriority priority) - { - var adapter = TypedBindingAdapter.Create(o, this, source); - return o.Bind(this, adapter, priority); - } - private object? GetDefaultBoxedValue(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs index a51899617b..232b6ccf73 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs @@ -156,14 +156,6 @@ namespace Avalonia.Base.UnitTests throw new NotImplementedException(); } - internal override IDisposable RouteBind( - AvaloniaObject o, - IObservable> source, - BindingPriority priority) - { - throw new NotImplementedException(); - } - internal override void RouteClearValue(AvaloniaObject o) { throw new NotImplementedException();