Browse Source

Use LightweightObservableBase for BindingExpression.

Steven Kirk 7 years ago
parent
commit
a3dea23560

+ 37 - 6
src/Avalonia.Base/Data/Core/BindingExpression.cs

@@ -7,6 +7,7 @@ using System.Reactive.Linq;
 using System.Reactive.Subjects;
 using Avalonia.Data.Converters;
 using Avalonia.Logging;
+using Avalonia.Reactive;
 using Avalonia.Utilities;
 
 namespace Avalonia.Data.Core
@@ -15,13 +16,14 @@ namespace Avalonia.Data.Core
     /// Binds to an expression on an object using a type value converter to convert the values
     /// that are send and received.
     /// </summary>
-    public class BindingExpression : ISubject<object>, IDescription
+    public class BindingExpression : LightweightObservableBase<object>, ISubject<object>, IDescription
     {
         private readonly ExpressionObserver _inner;
         private readonly Type _targetType;
         private readonly object _fallbackValue;
         private readonly BindingPriority _priority;
-        private readonly Subject<object> _errors = new Subject<object>();
+        InnerListener _innerListener;
+        WeakReference<object> _value;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@@ -139,7 +141,7 @@ namespace Avalonia.Data.Core
                                 "IValueConverter should not return non-errored BindingNotification.");
                         }
 
-                        _errors.OnNext(notification);
+                        PublishNext(notification);
 
                         if (_fallbackValue != AvaloniaProperty.UnsetValue)
                         {
@@ -170,12 +172,18 @@ namespace Avalonia.Data.Core
             }
         }
 
-        /// <inheritdoc/>
-        public IDisposable Subscribe(IObserver<object> observer)
+        protected override void Initialize() => _innerListener = new InnerListener(this);
+        protected override void Deinitialize() => _innerListener.Dispose();
+
+        protected override void Subscribed(IObserver<object> observer, bool first)
         {
-            return _inner.Select(ConvertValue).Merge(_errors).Subscribe(observer);
+            if (!first && _value != null && _value.TryGetTarget(out var val) == true)
+            {
+                observer.OnNext(val);
+            }
         }
 
+        /// <inheritdoc/>
         private object ConvertValue(object value)
         {
             var notification = value as BindingNotification;
@@ -301,5 +309,28 @@ namespace Avalonia.Data.Core
 
             return a;
         }
+
+        public class InnerListener : IObserver<object>, IDisposable
+        {
+            private readonly BindingExpression _owner;
+            private readonly IDisposable _dispose;
+
+            public InnerListener(BindingExpression owner)
+            {
+                _owner = owner;
+                _dispose = owner._inner.Subscribe(this);
+            }
+
+            public void Dispose() => _dispose.Dispose();
+            public void OnCompleted() => _owner.PublishCompleted();
+            public void OnError(Exception error) => _owner.PublishError(error);
+
+            public void OnNext(object value)
+            {
+                var converted = _owner.ConvertValue(value);
+                _owner._value = new WeakReference<object>(converted);
+                _owner.PublishNext(converted);
+            }
+        }
     }
 }

+ 15 - 0
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs

@@ -337,6 +337,21 @@ namespace Avalonia.Base.UnitTests.Data.Core
             GC.KeepAlive(data);
         }
 
+        [Fact]
+        public void Second_Subscription_Should_Fire_Immediately()
+        {
+            var data = new Class1 { StringValue = "foo" };
+            var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(string));
+            object result = null;
+
+            target.Subscribe();
+            target.Subscribe(x => result = x);
+
+            Assert.Equal("foo", result);
+
+            GC.KeepAlive(data);
+        }
+
         private class Class1 : NotifyingBase
         {
             private string _stringValue;