السؤال DictionarySubject and field changes

  • Tuesday, January 22, 2013 9:48 AM
     
      Has Code

    I'm subscribing to changes to an IConnectableObservable created from a DictionarySubject

                var subscription1 = hotObservable
                                        .Where(change => !change.HasValue || requiredValues.Contains(change.Value.Key))
                                        .SelectMany(change => change.ToModifications())
                                        .Subscribe(mod => {
                                            mod.Accept(targetDict);
                                        });

    With this I get notifications for add, remove, and entirely substitutions of values in the dictionary subject.

    The dictionary subject is defined with custom classes: DictionarySubject<CustomKey,CustomValue>.

    I would also like to subscribe to changes in private fields in addition to mentioned modifications to the collection itself.

    So if I call customValue.MethodThatChangesThings(), and changes the state of the object, subscriber could make some processing (not the trivial one showed in the code sample).

    Regards.


All Replies

  • Tuesday, January 22, 2013 11:24 AM
     
     
    How do you expect the cache to know when the object has been modified?

    Lee Campbell http://LeeCampbell.blogspot.com

  • Tuesday, January 22, 2013 12:53 PM
     
     
    I'm sorry I don't understand the question
  • Tuesday, January 22, 2013 1:53 PM
     
      Has Code

    You have written an observable query over a dictionary (well, a DictionarySubject) which is being used to cache your CustomValue types. Your question implies that you want this query over your cache to also yield results when there is a change to one of the values in the cache. I think Lee is just asking you how you expect the cache to be aware of these changes? i.e. You can't.

    Even without RX, you don't expect an ICollection to be directly aware of mutations to the objects it stores, so no query over the collection could produce that sort of data.

    To do what you want, you will need to introduce new events or observable streams on the CustomValue types that you store in your dictionary, and subscribe to those also. You could then compose your query to ensure that you subscribe to each value which is placed in your dictionary:

    //Crude - but to show you what I mean
    from value in dictionarySubject
    from valueUpdate in value.GetUpdatesObservable() //something new you are missing
    select valueUpdate

    • Edited by h_andr Tuesday, January 22, 2013 1:55 PM
    •  
  • Tuesday, January 22, 2013 5:25 PM
     
     

    Sorry for being terse. @H_andr makes my point better.

    Does your DataType that your Dictionary store expose any way to notify of a change in state? Common interfaces to do this are INotifyPropertyChanged and obviously IObservable<T>. How do you plan to expose notifications of changes?

    Lee


    Lee Campbell http://LeeCampbell.blogspot.com

  • Tuesday, January 22, 2013 5:50 PM
     
      Has Code

    Hi,

    As shown by h_andr, for every new element you'll need to call some operator that generates notifications for changes.  You may also want to unsubscribe when an element is removed.

    Rxx 2.0 provides new extension methods that make this possible with relatively little code.  If you're building Rxx 2.0 from the source code, then you already have access to these new extensions: PropertyChanges.  The 3rd overload extends IObservable<CollectionNotification<TSource>>, so it can automatically unsubscribe when elements are removed.  There are also overloads that track child dependencies.

    Note that these extensions only notify about changes to properties via the typical property change notification patterns in .NET, including, but not limited to, implementations of INotifyPropertyChanged.  The details are described in Rxx's reference documentation.  See also the documentation for FromPropertyChangedPattern.

    Here's a new Rxx lab to illustrate its usage; however, I've just found a bug that causes duplicate change notifications.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Reactive;
    using System.Reactive.Linq;
    using System.Reactive.Subjects;
    using System.Runtime.CompilerServices;
    
    namespace RxLabs.Reactive
    {
    	// http://social.msdn.microsoft.com/Forums/en/rx/thread/0fbf8cdb-0d91-41ae-bca6-ef28cad39896
    	public sealed class PropertyChangesLab : BaseConsoleLab
    	{
    		protected override void Main()
    		{
    			var hotObservable = new DictionarySubject<string, CustomValue>();
    			var targetDict = new Dictionary<string, CustomValue>();
    			var requiredValues = new[] { "A", "C" };
    
    			using (hotObservable
    				.Where(change => change.HasValue && requiredValues.Contains(change.Value.Key))
    				.Select(n =>
    					{
    						// PropertyChanges doesn't work on KeyValuePair because it doesn't have any property change events.
    						// So we must strip off the key and create a sequence of collection notifications for the values only.
    						switch (n.Kind)
    						{
    							case CollectionNotificationKind.OnAdded:
    								return CollectionNotification.CreateOnAdded(n.Value.Value);
    							case CollectionNotificationKind.OnRemoved:
    								return CollectionNotification.CreateOnRemoved(n.Value.Value);
    							case CollectionNotificationKind.OnReplaced:
    								return CollectionNotification.CreateOnReplaced(n.ReplacedValue.Value, n.Value.Value);
    							default:
    								throw new InvalidOperationException();
    						}
    					})
    				.PropertyChanges()
    				.Select(e => (CustomValue) e.Sender)
    				.Subscribe(value => TraceLine("Changed: {0}={1}", value.Name, value.Foo)))
    			{
    				var a = new CustomValue("A");
    				var b = new CustomValue("B");
    				var c = new CustomValue("C");
    
    				hotObservable.Add(a.Name, a);
    				hotObservable.Add(b.Name, b);
    				hotObservable.Add(c.Name, c);
    
    				a.Foo = 1;
    				b.Foo = 1;
    				c.Foo = 1;
    
    				hotObservable.Remove(a.Name);
    
    				a.Foo = 2;
    				b.Foo = 2;
    				c.Foo = 2;
    
    				hotObservable.Remove(b.Name);
    				hotObservable.Remove(c.Name);
    
    				a.Foo = 4;
    				b.Foo = 4;
    				c.Foo = 4;
    			}
    		}
    
    		private sealed class CustomValue : NotifyPropertyChanged
    		{
    			public string Name
    			{
    				get;
    				private set;
    			}
    
    			public int Foo
    			{
    				get
    				{
    					return foo;
    				}
    				set
    				{
    					SetProperty(ref foo, value);
    				}
    			}
    
    			private int foo;
    
    			public CustomValue(string name)
    			{
    				Name = name;
    			}
    		}
    
    		private abstract class NotifyPropertyChanged : INotifyPropertyChanged
    		{
    			public event PropertyChangedEventHandler PropertyChanged;
    
    			// C# 5.0
    			protected void SetProperty<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
    			{
    				if (!object.Equals(backingField, value))
    				{
    					backingField = value;
    					OnPropertyChanged(propertyName);
    				}
    			}
    
    			// C# 5.0
    			protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    			{
    				OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    			}
    
    			protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    			{
    				var handler = PropertyChanged;
    
    				if (handler != null)
    				{
    					handler(this, e);
    				}
    			}
    		}
    	}
    }

    Output: (With Bug)

    Changed: A=1
    Changed: A=1
    Changed: C=1
    Changed: C=1
    Changed: C=2
    Changed: C=2

    Note the duplicate notifications - that's the bug.  I'll fix this ASAP.

    To workaround the issue now, simply pass in a dependency selector that yields no objects.  For example:

    .PropertyChanges(_ => Enumerable.Empty<object>())

    Output:

    Changed: A=1
    Changed: C=1
    Changed: C=2

    - Dave


    http://davesexton.com/blog