/// <summary> /// Specifies a callback when properties change. /// </summary> /// <param name="callback">The callback.</param> /// <param name="publisher">The publisher.</param> /// <param name="propertyNames">The property names.</param> internal static void WhenChanged(this Action callback, INotifyPropertyChanged publisher, params string[] propertyNames) { var bindPropertyChanged = false; lock (SyncLock) { // Create the subscription set for the publisher if it does not exist. if (Subscriptions.ContainsKey(publisher) == false) { Subscriptions[publisher] = new SubscriptionSet(); // if it did not exist before, we need to bind to the // PropertyChanged event of the publisher. bindPropertyChanged = true; } // Save the Action reference so that the weak reference is not lost // TODO: The references will never be detected as dead if we pin them // but for the purposes of this sample app we don't need to remove the // dead references. PinnedActions[callback] = true; foreach (var propertyName in propertyNames) { // Create the set of callback references for the publisher's property if it does not exist. if (Subscriptions[publisher].ContainsKey(propertyName) == false) { Subscriptions[publisher][propertyName] = new CallbackReferenceSet(); } // Add the callback for the publisher's property changed Subscriptions[publisher][propertyName].Add(new CallbackReference(callback)); } } // No need to bind to the PropertyChanged event if we are already bound to it. if (bindPropertyChanged == false) { return; } // Finally, bind to property changed publisher.PropertyChanged += (s, e) => { var deadCallbacks = new CallbackReferenceSet(); var aliveCallbacks = new CallbackReferenceSet(); lock (SyncLock) { // we don't need to perform any action if there are no subscriptions to // this property name. if (Subscriptions[publisher].ContainsKey(e.PropertyName) == false) { return; } // Get the list of alive subscriptions for this property name aliveCallbacks.AddRange(Subscriptions[publisher][e.PropertyName]); } // Call the subscription's callbacks foreach (var aliveSubscription in aliveCallbacks) { // Check if the subscription reference is alive. if (aliveSubscription.IsAlive == false) { deadCallbacks.Add(aliveSubscription); continue; } // if the subscription is alive, invoke the matching action aliveSubscription.Target?.Invoke(); } // Skip over if we don't have dead subscriptions if (deadCallbacks.Count == 0) { return; } // Remove dead subscriptions lock (SyncLock) { foreach (var deadSubscriber in deadCallbacks) { Subscriptions[publisher][e.PropertyName].Remove(deadSubscriber); } } }; }
internal static void WhenChanged(this Action callback, bool pinned, INotifyPropertyChanged publisher, params string[] propertyNames) { var bindPropertyChanged = false; using (Locker.AcquireWriterLock()) { if (Subscriptions.ContainsKey(publisher) == false) { Subscriptions[publisher] = new SubscriptionSet(); bindPropertyChanged = true; } // Save the Action reference so that the weak reference is not lost if (pinned) { PinnedActions[callback] = true; } foreach (var propertyName in propertyNames) { if (Subscriptions[publisher].ContainsKey(propertyName) == false) { Subscriptions[publisher][propertyName] = new CallbackReferenceSet(); } Subscriptions[publisher][propertyName].Add(new CallbackReference(callback)); } } if (bindPropertyChanged == false) { return; } // Finally, bind to property changed publisher.PropertyChanged += (s, e) => { if (Subscriptions[publisher].ContainsKey(e.PropertyName) == false) { return; } var deadCallbacks = new CallbackReferenceSet(); var aliveCallbacks = new CallbackReferenceSet(); using (Locker.AcquireReaderLock()) { aliveCallbacks.AddRange(Subscriptions[publisher][e.PropertyName]); } foreach (var aliveSubscription in aliveCallbacks) { if (aliveSubscription.IsAlive == false) { deadCallbacks.Add(aliveSubscription); continue; } aliveSubscription.Target?.Invoke(); } if (deadCallbacks.Count == 0) { return; } using (Locker.AcquireWriterLock()) { foreach (var deadSubscriber in deadCallbacks) { Subscriptions[publisher][e.PropertyName].Remove(deadSubscriber); } } }; }