public void AddNestedMerge(int n)
        {
            var source = new ObservableCollection <Fake>();
            var path   = NotifyingPath.GetOrCreate <Fake, int>(x => x.Next.Next.Value);

            using (var view = source.AsMappingView(x => x.ObservePropertyChanged(path, signalInitial: true)))
            {
                var sw = Stopwatch.StartNew();
                using (var subject = new Subject <IObservable <EventPattern <PropertyChangedEventArgs> > >())
                {
                    using (subject.Switch()
                           .Publish()
                           .RefCount()
                           .Subscribe(_ => { }))
                    {
                        for (var i = 0; i < n; i++)
                        {
                            var fake = new Fake();
                            source.Add(fake);
                            subject.OnNext(view.Merge());
                        }
                    }
                }

                sw.Stop();
                Console.WriteLine("// source.ObserveItemPropertyChanged(x => x.Next.Next.Value): {0} Adds took {1} ms {2:F3} ms each. {3}", n, sw.ElapsedMilliseconds, sw.Elapsed.TotalMilliseconds / n, DateTime.Now.ToShortDateString());
            }
        }
Exemple #2
0
        private static IDisposable ItemPropertyChangedCore <TCollection, TItem, TProperty, T>(
            this IObservable <TCollection> source,
            IObserver <T> observer,
            Expression <Func <TItem, TProperty> > property,
            Func <TItem, object, PropertyChangedEventArgs, SourceAndValue <INotifyPropertyChanged, TProperty>, T> create)
            where TCollection : class, IEnumerable <TItem>, INotifyCollectionChanged
            where TItem : class, INotifyPropertyChanged
        {
            var tracker = ItemsTracker.Create((TCollection)null, NotifyingPath.GetOrCreate(property));

            tracker.TrackedItemChanged += Handler;
            var subscription = source.Subscribe(x => tracker.UpdateSource(x));

            return(new CompositeDisposable(3)
            {
                Disposable.Create(() => tracker.TrackedItemChanged -= Handler),
                tracker,
                subscription,
            });

            void Handler(TItem item, object sender, PropertyChangedEventArgs args, SourceAndValue <INotifyPropertyChanged, TProperty> sourceAndValue)
            {
                observer.OnNext(create(item, sender, args, sourceAndValue));
            }
        }
Exemple #3
0
        /// <summary>
        /// Create an <see cref="ObservableAndCriteria"/> to be passed in as constructor argument.
        /// </summary>
        /// <param name="source">The source instance.</param>
        /// <param name="path">The property path to listen to changes for on source.</param>
        /// <param name="value">The value when satisfied.</param>
        /// <param name="compare">How to compare actual value and <paramref name="value"/>.</param>
        protected static ObservableAndCriteria For <TSource, TValue>(
            TSource source,
            Expression <Func <TSource, TValue> > path,
            TValue value,
            Func <Maybe <TValue>, TValue, bool?> compare)
            where TSource : class, INotifyPropertyChanged
        {
            Ensure.NotNull(source, nameof(source));
            Ensure.NotNull(path, nameof(path));
            var notifyingPath = NotifyingPath.GetOrCreate(path);

            return(new ObservableAndCriteria(
                       Observable.Create <object>(o =>
            {
                var tracker = notifyingPath.CreateTracker(source);
                void Handler(IPropertyTracker _, object __, PropertyChangedEventArgs args, SourceAndValue <INotifyPropertyChanged, TValue> ___) => o.OnNext(args);
                tracker.TrackedPropertyChanged += Handler;
                return Disposable.Create(() =>
                {
                    tracker.TrackedPropertyChanged -= Handler;
                    tracker.Dispose();
                });
            }),
                       () => compare(notifyingPath.SourceAndValue(source).Value, value)));
        }
Exemple #4
0
        private static IDisposable ObserveItemPropertyChangedCore <TCollection, TItem, TProperty, T>(
            this TCollection source,
            IObserver <T> o,
            Expression <Func <TItem, TProperty> > property,
            bool signalInitial,
            Func <TItem, object, PropertyChangedEventArgs, SourceAndValue <INotifyPropertyChanged, TProperty>, T> create)
            where TCollection : class, IEnumerable <TItem>, INotifyCollectionChanged
            where TItem : class, INotifyPropertyChanged
        {
            var tracker = ItemsTracker.Create(
                signalInitial
                    ? null
                    : source,
                NotifyingPath.GetOrCreate(property));

            TrackedItemPropertyChangedEventHandler <TItem, TProperty> handler =
                (item, sender, args, sourceAndValue) => o.OnNext(
                    create(
                        item,
                        sender,
                        args,
                        sourceAndValue));

            tracker.TrackedItemChanged += handler;
            if (signalInitial)
            {
                tracker.UpdateSource(source);
            }

            return(new CompositeDisposable(2)
            {
                Disposable.Create(() => tracker.TrackedItemChanged -= handler),
                tracker
            });
        }
        private static IObservable <T> ObservePropertyChangedCore <TNotifier, TProperty, T>(
            this TNotifier source,
            NotifyingPath <TNotifier, TProperty> notifyingPath,
            Func <object, PropertyChangedEventArgs, T> create,
            bool signalInitial = true)
            where TNotifier : class, INotifyPropertyChanged
        {
            if (signalInitial)
            {
                return(Observable.Return(
                           create(
                               notifyingPath.SourceAndValue(source).Source,
                               CachedEventArgs.GetOrCreatePropertyChangedEventArgs(string.Empty)))
                       .Concat(source.ObservePropertyChangedCore(notifyingPath, create, signalInitial: false)));
            }

            if (notifyingPath.Count > 1)
            {
                return(Observable.Create <T>(
                           o =>
                {
                    var tracker = notifyingPath.CreateTracker(source);
                    void Handler(IPropertyTracker _, object sender, PropertyChangedEventArgs e, SourceAndValue <INotifyPropertyChanged, TProperty> __) => o.OnNext(create(sender, e));
                    tracker.TrackedPropertyChanged += Handler;
                    return new CompositeDisposable(2)
                    {
                        tracker,
                        Disposable.Create(() => tracker.TrackedPropertyChanged -= Handler),
                    };
                }));
            }

            return(ObservePropertyChangedCore(source, notifyingPath.Last.Property.Name, create, signalInitial: false));
        }
        /// <summary>
        /// Observe property changes for the path <paramref name="source"/>.
        /// This signals when any of the items in tha path signals.
        /// </summary>
        /// <typeparam name="TNotifier">The source type.</typeparam>
        /// <typeparam name="TProperty">The property type.</typeparam>
        /// <param name="source">The source instance.</param>
        /// <param name="property">An expression specifying the property path.</param>
        /// <param name="signalInitial"> If true OnNext is called immediately on subscribe. </param>
        /// <returns>The <see cref="IObservable{T}"/>.</returns>
        public static IObservable <PropertyChangedEventArgs> ObserveFullPropertyPathSlim <TNotifier, TProperty>(this TNotifier source, Expression <Func <TNotifier, TProperty> > property, bool signalInitial = true)
            where TNotifier : class, INotifyPropertyChanged
        {
            if (source is null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (property is null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            var notifyingPath = NotifyingPath.GetOrCreate(property);

            if (notifyingPath.Count < 2)
            {
                var message = "Expected path to have more than one item.\r\n" +
                              $"The path was {property}\r\n" +
                              "Did you mean to call ObservePropertyChangedSlim?";
                throw new ArgumentException(message, nameof(property));
            }

            return(ObserveFullPropertyPathCore(source, notifyingPath, (_, e) => e, signalInitial));
        }
        private static IObservable <T> ObserveValueCore <TNotifier, TProperty, T>(
            this TNotifier source,
            NotifyingPath <TNotifier, TProperty> notifyingPath,
            Func <object?, PropertyChangedEventArgs, Maybe <TProperty>, T> create,
            bool signalInitial = true)
            where TNotifier : class, INotifyPropertyChanged
        {
            if (signalInitial)
            {
                return(Observable.Defer(
                           () =>
                {
                    var sourceAndValue = notifyingPath.SourceAndValue(source);
                    return Observable.Return(
                        create(
                            sourceAndValue.Source,
                            CachedEventArgs.GetOrCreatePropertyChangedEventArgs(string.Empty),
                            sourceAndValue.Value));
                })
                       .Concat(source.ObserveValueCore(notifyingPath, create, signalInitial: false)));
            }

            if (notifyingPath.Count > 1)
            {
                return(Observable.Create <T>(
                           o =>
                {
                    var tracker = notifyingPath.CreateTracker(source);
                    tracker.TrackedPropertyChanged += Handler;
                    return new CompositeDisposable(2)
                    {
                        tracker,
                        Disposable.Create(() => tracker.TrackedPropertyChanged -= Handler),
                    };

                    void Handler(IPropertyTracker _, object sender, PropertyChangedEventArgs args, SourceAndValue <INotifyPropertyChanged?, TProperty> sourceAndValue)
                    {
                        o.OnNext(create(sender, args, sourceAndValue.Value));
                    }
                }));
            }

            return(Observable.Create <T>(
                       o =>
            {
                void Handler(object sender, PropertyChangedEventArgs e)
                {
                    if (e.IsMatch(notifyingPath.Last.Property))
                    {
                        var value = notifyingPath.Last.GetMaybe(sender);
                        o.OnNext(create(sender, e, value));
                    }
                }

                source.PropertyChanged += Handler;
                return Disposable.Create(() => source.PropertyChanged -= Handler);
            }));
        }
        public void ToStringTest()
        {
            var path = NotifyingPath.GetOrCreate <Fake, int>(x => x.Level1.Value);

            Assert.AreEqual("x => x.Level1.Value", path.ToString());

            path = NotifyingPath.GetOrCreate <Fake, int>(x => x.Level1.Level2.Value);
            Assert.AreEqual("x => x.Level1.Level2.Value", path.ToString());
        }
 /// <summary>
 /// Extension method for listening to property changes.
 /// Handles nested x => x.Level1.Level2.Level3
 /// Unsubscribes &amp; subscribes when each level changes.
 /// Handles nulls.
 /// </summary>
 /// <param name="source">The source instance to track changes for. </param>
 /// <param name="propertyPath">
 /// A cached property path. Creating the property path from Expression&lt;Func&lt;TNotifier, TProperty&gt;&gt; is a bit expensive so caching can make sense.
 ///  </param>
 /// <param name="signalInitial">
 /// If true OnNext is called immediately on subscribe.
 /// </param>
 internal static IObservable <EventPattern <PropertyChangedEventArgs> > ObservePropertyChanged <TNotifier, TProperty>(
     this TNotifier source,
     NotifyingPath <TNotifier, TProperty> propertyPath,
     bool signalInitial = true)
     where TNotifier : class, INotifyPropertyChanged
 {
     return(ObservePropertyChangedCore(
                source,
                propertyPath,
                (sender, args) => new EventPattern <PropertyChangedEventArgs>(sender, args),
                signalInitial));
 }
        public void ToStringTest()
        {
#pragma warning disable CS8602 // Dereference of a possibly null reference.
            var path = NotifyingPath.GetOrCreate <Fake, int>(x => x.Level1.Value);
#pragma warning restore CS8602 // Dereference of a possibly null reference.
            Assert.AreEqual("x => x.Level1.Value", path.ToString());

#pragma warning disable CS8602 // Dereference of a possibly null reference.
            path = NotifyingPath.GetOrCreate <Fake, int>(x => x.Level1.Level2.Value);
#pragma warning restore CS8602 // Dereference of a possibly null reference.
            Assert.AreEqual("x => x.Level1.Level2.Value", path.ToString());
        }
Exemple #11
0
 internal static IObservable <EventPattern <PropertyChangedAndValueEventArgs <TProperty> > > ObservePropertyChangedWithValue <TNotifier, TProperty>(
     this TNotifier source,
     NotifyingPath <TNotifier, TProperty> propertyPath,
     bool signalInitial = true)
     where TNotifier : class, INotifyPropertyChanged
 {
     return(source.ObserveValueCore(
                propertyPath,
                (sender, e, value) => new EventPattern <PropertyChangedAndValueEventArgs <TProperty> >(
                    sender,
                    new PropertyChangedAndValueEventArgs <TProperty>(e.PropertyName, value)),
                signalInitial));
 }
        /// <summary>
        /// Observe propertychanges with values.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="property">An expression specifying the property path.</param>
        /// <param name="signalInitial">If true OnNext is called immediately on subscribe.</param>
        /// <typeparam name="TNotifier">The type of <paramref name="source"/>.</typeparam>
        /// <typeparam name="TProperty">The type of the last property in the path.</typeparam>
        /// <returns>The <see cref="IObservable{T}"/> of type of type <see cref="EventPattern{TArgs}"/> of type <see cref="PropertyChangedAndValueEventArgs{TProperty}"/>.</returns>
        public static IObservable <EventPattern <PropertyChangedAndValueEventArgs <TProperty> > > ObservePropertyChangedWithValue <TNotifier, TProperty>(
            this TNotifier source,
            Expression <Func <TNotifier, TProperty> > property,
            bool signalInitial = true)
            where TNotifier : class, INotifyPropertyChanged
        {
            Ensure.NotNull(source, nameof(source));
            Ensure.NotNull(property, nameof(property));

            var notifyingPath = NotifyingPath.GetOrCreate(property);

            return(source.ObservePropertyChangedWithValue(notifyingPath, signalInitial));
        }
        /// <summary>
        /// Extension method for listening to property changes.
        /// Handles nested x => x.Level1.Level2.Level3
        /// Unsubscribes &amp; subscribes when each level changes.
        /// Handles nulls.
        /// </summary>
        /// <param name="source">The source instance to track changes for. </param>
        /// <param name="property">
        /// An expression specifying the property path to track.
        /// Example x => x.Foo.Bar.Meh.
        ///  </param>
        /// <param name="signalInitial">
        /// If true OnNext is called immediately on subscribe.
        /// </param>
        public static IObservable <PropertyChangedEventArgs> ObservePropertyChangedSlim <TNotifier, TProperty>(
            this TNotifier source,
            Expression <Func <TNotifier, TProperty> > property,
            bool signalInitial = true)
            where TNotifier : class, INotifyPropertyChanged
        {
            Ensure.NotNull(source, nameof(source));
            Ensure.NotNull(property, nameof(property));

            var notifyingPath = NotifyingPath.GetOrCreate(property);

            return(ObservePropertyChangedCore(source, notifyingPath, (_, e) => e, signalInitial));
        }
        public NestedChanges(TCollection source, Expression <Func <TItem, TValue> > selector)
        {
            var path = NotifyingPath.GetOrCreate(selector);

            this.source = new MappingView <TItem, ValueTracker>(
                source: source,
                factory: Mapper.Create <TItem, ValueTracker>(x => new ValueTracker(this, x, path), x => x.Dispose()),
                bufferTime: TimeSpan.Zero,
                scheduler: null,
                leaveOpen: true);
            this.subscription = source.ObserveCollectionChangedSlim(signalInitial: false)
                                .Subscribe(this.OnSourceChanged);
        }
        /// <summary>
        /// Extension method for listening to property changes.
        /// Handles nested x => x.Level1.Level2.Level3
        /// Unsubscribes &amp; subscribes when each level changes.
        /// Handles nulls.
        /// </summary>
        /// <param name="source">The source instance to track changes for. </param>
        /// <param name="property">
        /// An expression specifying the property path to track.
        /// Example x => x.Foo.Bar.Meh.
        ///  </param>
        /// <param name="signalInitial">
        /// If true OnNext is called immediately on subscribe.
        /// </param>
        public static IObservable <Maybe <TProperty> > ObserveValue <TNotifier, TProperty>(
            this TNotifier source,
            Expression <Func <TNotifier, TProperty> > property,
            bool signalInitial = true)
            where TNotifier : class, INotifyPropertyChanged
        {
            Ensure.NotNull(source, nameof(source));
            Ensure.NotNull(property, nameof(property));

            var notifyingPath = NotifyingPath.GetOrCreate(property);

            return(source.ObserveValueCore(
                       notifyingPath,
                       (_, __, value) => value,
                       signalInitial)
                   .DistinctUntilChanged());
        }
Exemple #16
0
        public void AddNested(int n)
        {
            var source = new ObservableCollection <Fake>();
            var path   = NotifyingPath.GetOrCreate <Fake, int>(x => x.Next.Next.Value);

            using var view = source.AsMappingView(x => x.ObservePropertyChanged(path, signalInitial: true));
            var sw = Stopwatch.StartNew();

            for (var i = 0; i < n; i++)
            {
                var fake = new Fake();
                source.Add(fake);
            }

            sw.Stop();
            Console.WriteLine("// source.ObserveItemPropertyChanged(x => x.Next.Next.Value): {0} Adds took {1} ms {2:F3} ms each. {3}", n, sw.ElapsedMilliseconds, sw.Elapsed.TotalMilliseconds / n, DateTime.Now.ToShortDateString());
        }
        /// <summary>
        /// Extension method for listening to property changes.
        /// Handles nested x => x.Level1.Level2.Level3
        /// Unsubscribes &amp; subscribes when each level changes.
        /// Handles nulls.
        /// </summary>
        /// <typeparam name="TNotifier">The source type.</typeparam>
        /// <typeparam name="TProperty">The property type.</typeparam>
        /// <param name="source">The source instance to track changes for. </param>
        /// <param name="property">
        /// An expression specifying the property path to track.
        /// Example x => x.Foo.Bar.Meh.
        ///  </param>
        /// <param name="signalInitial">
        /// If true OnNext is called immediately on subscribe.
        /// </param>
        /// <returns>An <see cref="IObservable{T}"/>.</returns>
        public static IObservable <PropertyChangedEventArgs> ObservePropertyChangedSlim <TNotifier, TProperty>(
            this TNotifier source,
            Expression <Func <TNotifier, TProperty> > property,
            bool signalInitial = true)
            where TNotifier : class, INotifyPropertyChanged
        {
            if (source is null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (property is null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            var notifyingPath = NotifyingPath.GetOrCreate(property);

            return(ObservePropertyChangedCore(source, notifyingPath, (_, e) => e, signalInitial));
        }
Exemple #18
0
        public static IObservable <EventPattern <PropertyChangedAndValueEventArgs <TProperty> > > ObservePropertyChangedWithValue <TNotifier, TProperty>(
            this TNotifier source,
            Expression <Func <TNotifier, TProperty> > property,
            bool signalInitial = true)
            where TNotifier : class, INotifyPropertyChanged
        {
            if (source is null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (property is null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            var notifyingPath = NotifyingPath.GetOrCreate(property);

            return(source.ObservePropertyChangedWithValue(notifyingPath, signalInitial));
        }
        /// <summary>
        /// Extension method for listening to property changes.
        /// Handles nested x => x.Level1.Level2.Level3
        /// Unsubscribes &amp; subscribes when each level changes.
        /// Handles nulls.
        /// </summary>
        /// <typeparam name="TNotifier">The source type.</typeparam>
        /// <typeparam name="TProperty">The property type.</typeparam>
        /// <param name="source">The source instance to track changes for. </param>
        /// <param name="property">
        /// An expression specifying the property path to track.
        /// Example x => x.Foo.Bar.Meh.
        ///  </param>
        /// <param name="signalInitial">
        /// If true OnNext is called immediately on subscribe.
        /// </param>
        /// <returns>The <see cref="IObservable{T}"/>.</returns>
        public static IObservable <Maybe <TProperty> > ObserveValue <TNotifier, TProperty>(
            this TNotifier source,
            Expression <Func <TNotifier, TProperty> > property,
            bool signalInitial = true)
            where TNotifier : class, INotifyPropertyChanged
        {
            if (source is null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (property is null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            var notifyingPath = NotifyingPath.GetOrCreate(property);

            return(source.ObserveValueCore(
                       notifyingPath,
                       (_, _, value) => value,
                       signalInitial)
                   .DistinctUntilChanged());
        }
        private static IObservable <T> ObserveFullPropertyPathCore <TNotifier, TProperty, T>(this TNotifier source, NotifyingPath <TNotifier, TProperty> notifyingPath, Func <object, PropertyChangedEventArgs, T> create, bool signalInitial = true)
            where TNotifier : class, INotifyPropertyChanged
        {
            if (signalInitial)
            {
                return(Observable.Return(
                           create(
                               notifyingPath.SourceAndValue(source)
                               .Source,
                               CachedEventArgs.GetOrCreatePropertyChangedEventArgs(string.Empty)))
                       .Concat(source.ObserveFullPropertyPathCore(notifyingPath, create, signalInitial: false)));
            }

            return(Observable.Create <T>(
                       o =>
            {
#pragma warning disable IDISP001  // Dispose created.
                var tracker = notifyingPath.CreateTracker(source);
#pragma warning restore IDISP001  // Dispose created.
                void Handler(object sender, PropertyChangedEventArgs e) => o.OnNext(create(sender, e));
                foreach (var propertyTracker in tracker)
                {
                    propertyTracker.TrackedPropertyChanged += Handler;
                }

                return Disposable.Create(
                    () =>
                {
                    foreach (var propertyTracker in tracker)
                    {
                        propertyTracker.TrackedPropertyChanged -= Handler;
                    }

                    tracker.Dispose();
                });
            }));
        }
Exemple #21
0
 public object NotifyingPathGetOrCreateTwoItemPath()
 {
     return(NotifyingPath.GetOrCreate(TwoItemPath));
 }
Exemple #22
0
 public object NotifyingPathGetOrCreateSingleItemPath()
 {
     return(NotifyingPath.GetOrCreate(SingleItemPath));
 }