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()); } }
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)); } }
/// <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))); }
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 & 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<Func<TNotifier, TProperty>> 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()); }
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 & 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 & 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()); }
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 & 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)); }
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 & 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(); }); })); }
public object NotifyingPathGetOrCreateTwoItemPath() { return(NotifyingPath.GetOrCreate(TwoItemPath)); }
public object NotifyingPathGetOrCreateSingleItemPath() { return(NotifyingPath.GetOrCreate(SingleItemPath)); }