Skip to content

Cross-platform .NET library for computations whose arguments and results are objects that implement INotifyPropertyChanged and INotifyCollectionChanged (ObservableCollection) interfaces.

License

Notifications You must be signed in to change notification settings

IgorBuchelnikov/ObservableComputations

Repository files navigation

Coverage Status

Nuget package newest version Nuget package downloads number

chat

ObservableComputations

What I should know to read this guide? To understand written here you should know basic programming and OOP concepts, C# syntax (including events, extension methods, lambda expressions), LINQ, INotifyPropertyChanged, INotifyCollectionChanged, IDisposable interfaces.

It is advisable to know the differences between delegates and expression trees.

To imagine the benefits of using ObservableComputations you should know about binding in WPF (or in other UI platforms: Xamarin, Blazor), especially in relation to INotifyPropertyChanged and INotifyCollectionChanged interfaces, Entity framework`s DbSet.Local property (local data), asynchronous querying in Entity framework.

What is ObservableComputations?

This is a cross-platform .NET library for computations whose arguments and results are objects that implement INotifyPropertyChanged and INotifyCollectionChanged (ObservableCollection) interfaces. The computations include ones similar to LINQ, the computation of arbitrary expression and additional features. The computations are implemented as extension methods, like LINQ ones. You can combine calls of ObservableComputations extension methods including chaining and nesting, as you do for LINQ methods. Computations in background threads, including parallel ones, as well as time-related processing of CollectionChanged and PropertyChanged events, are supported.

Why ObservableComputations?

ObservableComputations is easy to use and powerful implementation of reactive programming paradigm. With ObservableComputations, your code will fit more to the functional (declarative) style than with standard LINQ. Reactive programming in the functional style makes your code clearer, shorter, more reliable, and more performant. With reactive programming, you can develop rich UI faster. See details in Use cases and benefits section.

Demo applications

Analogs

The closest analogs of ObservableComputations are the following libraries: Obtics, OLinq, NFM.Expressions, BindableLinq, ContinuousLinq.

Details ObservableComputations is not analog of Reactive Extensions. The main distinguish ObservableComputations from Reactive Extensions is the following:
  • Reactive Extensions is abstracted from event specific and event semantics: it is a framework for processing all possible events. Reactive Extensions handles all events in the same way and all specifics are only in user code. ObservableComputations is focused on CollectionChanged and PropertyChanged events only and brings great benefit processing these events.
  • Reactive Extensions library provides a stream of events. ObservableComputations library provides not only the stream of data change events but a currently computed data.

Some of the tasks that you solved using Reactive Extensions are now easier and more efficient to solve using ObservableComputations. You can use ObservableComputations separately or in cooperation with Reactive Extensions. Observable Computations will not replace Reactive Extensions:

Details The ReactiveUI library (and its DynamicData sub-library) are not abstracted from the INotifyPropertyChanged and INotifyCollectionChanged interfaces and when working with these interfaces allows you to do much the same things as ObservableComputations, but ObservableComputations are less verbose, easier to use, more declarative, less touches the source data. Why?

You can compare these libraries and ObservableComputations in action, see

Status

All functions and operators needed to develop real applications have been implemented.

Where can I download? How to install? Where can I see the change log?

All ObservableComputations releases are available at NuGet. There you can also see the history of changes in the Release Notes section.

How can I get help or leave feedback?

How can I contribute?

  • Presentations, blog posts, tutorials, feedbacks are needed
  • Documentation comments and corrections are welcome (I'm not an English speaker)
  • Create a bug report
  • Propose a new feature
  • Create a demo application
  • Create a unit test
  • Create xml documentation in the code
  • A pretty icon is needed

Quick start

After reviewing these examples, you can start using ObservableComputations. The rest of this guide can be read as needed.

LINQ methods analogs

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		public event PropertyChangedEventHandler PropertyChanged;

		public int Num {get; set;}

		private decimal _price;
		public decimal Price
		{
			get => _price;
			set
			{
				_price = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Price)));
			}
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			ObservableCollection<Order> orders = 
				new ObservableCollection<Order>(new []
				{
					new Order{Num = 1, Price = 15},
					new Order{Num = 2, Price = 15},
					new Order{Num = 3, Price = 25},
					new Order{Num = 4, Price = 27},
					new Order{Num = 5, Price = 30},
					new Order{Num = 6, Price = 75},
					new Order{Num = 7, Price = 80}
				});

			// We start using ObservableComputations here!
			OcConsumer consumer = new OcConsumer();

			Filtering<Order> expensiveOrders = 
				orders
				.Filtering(o => o.Price > 25)
				.For(consumer); 
			
			Debug.Assert(expensiveOrders is ObservableCollection<Order>);
			
			checkFiltering(orders, expensiveOrders); // Prints "True"

			expensiveOrders.CollectionChanged += (sender, eventArgs) =>
			{
				// see the changes (add, remove, replace, move, reset) here			
				checkFiltering(orders, expensiveOrders); // Prints "True"
			};

			// Start the changing...
			orders.Add(new Order{Num = 8, Price = 30});
			orders.Add(new Order{Num = 9, Price = 10});
			orders[0].Price = 60;
			orders[4].Price = 10;
			orders.Move(5, 1);
			orders[1] = new Order{Num = 10, Price = 17};

			checkFiltering(orders, expensiveOrders); // Prints "True"

			Console.ReadLine();

			consumer.Dispose();
		}

		static void checkFiltering(
			ObservableCollection<Order> orders, 
			Filtering<Order> expensiveOrders)
		{
			Console.WriteLine(expensiveOrders.SequenceEqual(
				orders.Where(o => o.Price > 25)));
		}
	}
}

As you can see Filtering extension method is an analog of Where method from LINQ. Filtering extension method returns an instance of Filtering<Order> class. Filtering<TSourceItem> class implements INotifyCollectionChanged interface and derived from ObservableCollection<TSourceItem>. Examining the code above you can see expensiveOrders is not recomputed from scratch whenever the orders collection change or Price property of some order changed, in the expensiveOrders collection occurs only that changes, that relevant to a particular change in the orders collection or Price property of some order. Referring to reactive programming terminology, this behavior defines the change propagation algorithm as "push".

In the code above, during the execution of For extension method, the following events are subscribed: the CollectionChanged event of orders collection and PropertyChanged event of every instance of the Order class. During the execution of the consumer.Dispose() method, events are unsubscribed.

The complexity of predicate expression passed to Filtering method (o => o.Price > 25) is not limited. The expression can contain results of any ObservableComputations methods, including LINQ analogs.

Arbitrary expression observing

using System;
using System.ComponentModel;
using System.Diagnostics;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		public event PropertyChangedEventHandler PropertyChanged;

		public int Num {get; set;}

		private decimal _price;
		public decimal Price
		{
			get => _price;
			set
			{
				_price = value;
				PropertyChanged?.Invoke(this, 
					new PropertyChangedEventArgs(nameof(Price)));
			}
		}

		private byte _discount;
		public byte Discount
		{
			get => _discount;
			set
			{
				_discount = value;
				PropertyChanged?.Invoke(this, 
					new PropertyChangedEventArgs(nameof(Discount)));
			}
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			Console.OutputEncoding = System.Text.Encoding.UTF8;

			Order order = new Order{Num = 1, Price = 100, Discount = 10};

			// We start using ObservableComputations here!
			OcConsumer consumer = new OcConsumer();

			Computing<decimal> discountedPriceComputing = 
				new Computing<decimal>(
					() => order.Price - order.Price * order.Discount / 100)
				.For(consumer);
				
			Debug.Assert(discountedPriceComputing is INotifyPropertyChanged);

			printDiscountedPrice(discountedPriceComputing);

			discountedPriceComputing.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(Computing<decimal>.Value))
				{
					// see the changes here
					printDiscountedPrice(discountedPriceComputing);
				}
			};

			// Start the changing...
			order.Price = 200;
			order.Discount = 15;

			Console.ReadLine();

			consumer.Dispose();
		}

		static void printDiscountedPrice(Computing<decimal> discountedPriceComputing)
		{
			Console.WriteLine($"Discounted price is ₽{discountedPriceComputing.Value}");
		}
	}
}

In this code sample, we observe the value of discounted price expression. Computing<TResult> class implements INotifyPropertyChanged interface. The complexity of expression to observe is not limited. The expression can contain results of any ObservableComputations methods, including LINQ analogs.

Same as in the previous example during the execution of For extension method PropertyChanged event of Order class instance is subscribed. During the execution of the consumer.Dispose() method, events are unsubscribed.

If you want () => order.Price - order.Price * order.Discount / 100 to be a pure function, no problem:

Expression<Func<Order, decimal>> discountedPriceExpression = 
	o => o.Price - o.Price * o.Discount / 100;
	
// We start using ObservableComputations here!
Computing<decimal> discountedPriceComputing = 
	order.Using(discountedPriceExpression).For(consumer);

Now discountedPriceExpression can be reused for other instances of Order class.

Use cases and benefits

UI binding

WPF, Xamarin, Blazor. You can bind UI controls to the instances of ObservableComputations classes (Filtering, Computing, etc.). If you do it, you do not have to worry about forgetting to call PropertyChanged for the computed properties or manually process a change in some collection. With ObservableComputations, you define how the value should be computed (declarative style), everything else ObservableComputations will do.

Asynchronous programming

This approach facilitates asynchronous programming. You can show the user the UI form and in the background begin to load the source data (from DB or web service). As the source data loads, the UI form will be filled with the computed data. The end-user will see the UI form faster (while the source data is loaded in the background, you can start rendering). If the UI form is already shown to the user, you can also refresh the source data in the background, the computed data on the UI form will be refreshed thanks to ObservableComputations. ObservableComputations also include features for multi-threaded computing. See here for details.

Increased performance

If you have complex computations, over frequently changing source data and\or data is large, you can get increased performance with ObservableComputations, since you do not need to recompute value from scratch whenever source data gets some little change. Every little change in source data causes a little change in the data computed by ObservableComputations. UI performance is increased, as the need for re-rendering is reduced (only data that has changed is rendered) and data from external sources (DB, web service) is loaded in the background (see the previous section).

Clean and durable code

  • Less boilerplate imperative code. More clear declarative (functional style) code. Total code is reduced.
  • Less human error: computed data shown to the user will always correspond to the user input and the data loaded from external sources (DB, web service).
  • Source data loading code and UI data computation code can be clearly separated.
  • You do not need to worry about the fact that you forgot to update the calculated data. All calculated data will be updated automatically.

Friendly UI

ObservableComputations facilitate the design of friendly UI.

  • The user does not need to manually refresh computed data.
  • Users can see computed data always, not only by request.
  • You do not need to refresh computed data by the timer.
  • No need to block UI during the computation and rendering of a large amount of data (while showing a busy indicator). Data can be updated in small pieces, while the user can continue to work.

Full list of operators

Before examining the table below, please take into account

  • ScalarComputing<TValue> implements IReadScalar<TValue> interface;
public interface IReadScalar<out TValue> : System.ComponentModel.INotifyPropertyChanged
{
	TValue Value { get;}
}

Value property allows you to get a current result of a computation. From the code above you can see: ScalarComputation allows you to observe the changes of the Value property through PropertyChanged event of INotifyPropertyChanged interface.

MS LINQ analogs
ObservableComputations
overloaded
methods group
MS LINQ overloaded
methods group
Returned instance
class derived from
Note
Appending Append CollectionComputing
Aggregating Aggregate ScalarComputing
AllComputing All ScalarComputing
AnyComputing Any ScalarComputing
Averaging Average ScalarComputing
Casting Cast CollectionComputing
Concatenating Concat CollectionComputing Element of the source collection
may be INotifyCollectionChanged
or IReadScalar<INotifyCollectionChanged>
ContainsComputing Contains ScalarComputing
ObservableCollection
.Count property
Count

Not implemented DefaultIfEmpty

Distincting Distinct CollectionComputing
ItemComputing ElementAtOrDefault ScalarComputing If the index requested is out of the source collection
range ScalarComputing<TSourceItem>.Value
property returns default value
Excepting Except CollectionComputing
FirstComputing FirstOrDefault ScalarComputing If the source collection length is zero
ScalarComputing<TSourceItem>.Value property
returns default value
Grouping Group CollectionComputing Can contain a group with null key
GroupJoining GroupJoin CollectionComputing
PredicateGroupJoining CollectionComputing
IndicesComputing IndexOf CollectionComputing
Intersecting Intersect CollectionComputing
Joining Join CollectionComputing
LastComputing LastOrDefault ScalarComputing If the source collection length is zero
ScalarComputing<TSourceItem>.Value property
returns default value
Maximazing Max ScalarComputing If the source collection length is zero
ScalarComputing<TSourceItem>.Value property
returns default value
Minimazing Min ScalarComputing If the source collection length is zero
ScalarComputing<TSourceItem>.Value property
returns default value
OfTypeComputing OfType CollectionComputing
Ordering Order CollectionComputing
Ordering OrderByDescending CollectionComputing
Prepending Prepend CollectionComputing
SequenceComputing Range CollectionComputing
Reversing Reverse CollectionComputing
Selecting Select CollectionComputing
SelectingMany SelectMany CollectionComputing
Skiping Skip CollectionComputing
SkipingWhile SkipWhile CollectionComputing
StringsConcatenating string.Join ScalarComputing
Summarizing Sum ScalarComputing
Taking Take CollectionComputing
TakingWhile TakeWhile CollectionComputing
ThenOrdering ThenBy CollectionComputing
ThenOrdering ThenByDescending CollectionComputing
Dictionaring ToDictionary Dictionary
HashSetting ToHashSet HashSet
Uniting Union CollectionComputing
Filtering Where CollectionComputing
Zipping Zip CollectionComputing
Other features
ObservableComputations overloaded methods group Returned instance class derived from Note
Binding class
see more here
CollectionDispatching CollectionComputing see more here
CollectionDisposing CollectionComputing see more here
CollectionPausing CollectionComputing see more here
CollectionItemProcessing
CollectionItemsProcessing
CollectionComputing see more here
Computing ScalarComputing see more here
Differing ScalarComputing see more here
NullPropagating ScalarComputing Analog of «?.» operator.
This implementation is needed due to CS8072
Paging CollectionComputing contains a subset of collection elements
corresponding to a page
with a specific number and size
PreviousTracking ScalarComputing see more here
PropertyAccessing ScalarComputing see more here
PropertyDispatching ScalarComputing see more here
ScalarDispatching ScalarComputing see more here
ScalarDisposing ScalarComputing see more here
ScalarPausing ScalarComputing see more here
ScalarProcessing ScalarComputing see more here
Using ScalarComputing see more here and here
WeakPreviousTracking ScalarComputing see more here

For all computations having parameter of type INotifyCollectionChanged: null value of the parameter is treated as empty collection.

For the all computations having parameter of type IReadScalar<INotifyCollectionChanged>: null value of IReadScalar<INotifyCollectionChanged>.Value property is treated as an empty collection.

Two computation states: active and inactive

For computation to handle changes in its sources, it must subscribe to the PropertyChanged and CollectionChanged events of its sources. In this case, the computation is in the active state (IsActive == true). When you subscribe to an event, a link is created from the event source (computation source) to the event handler delegate. The delegate itself, in turn, refers to the object in the context of which it is executed (computation). Therefore, when active, computation sources refer to a computation. The computation also links to sources. This means that during garbage collection, an active computation can only be unloaded from memory together with its sources. In other words, in an active state, a computation can be unloaded from memory only if there are no references to either computation or sources. Sometimes situations arise when sources are needed (there are links to them), but the computation is no longer needed and must be unloaded from memory. This is only possible if the computation is unsubscribed from the PropertyChanged and CollectionChanged events of their sources. In this case, the computation is in an inactive state. In the inactive state, collection computations are empty, and scalar computations return a default value.

ObservableComputations has an API for controlling computation activity. The basic idea behind this is that when someone needs a computation, it is active. If no one needs the computation, it becomes inactive. The objects that may need computations are instances of the OcConsumer class:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		public event PropertyChangedEventHandler PropertyChanged;

		public int Num {get; set;}

		private decimal _price;
		public decimal Price
		{
			get => _price;
			set
			{
				_price = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Price)));
			}
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			ObservableCollection<Order> orders = 
				new ObservableCollection<Order>(new []
				{
					new Order{Num = 1, Price = 15},
					new Order{Num = 2, Price = 15},
					new Order{Num = 3, Price = 25},
					new Order{Num = 4, Price = 27},
					new Order{Num = 5, Price = 30},
					new Order{Num = 6, Price = 75},
					new Order{Num = 7, Price = 80}
				});

			// We start using ObservableComputations here!
			OcConsumer consumer = new OcConsumer();

			Selecting<Order, decimal> highPrices = 
				orders
					.Filtering(o => o.Price > 25)
					.Selecting(o => o.Price);
			
			// Computations is not active
			Debug.Assert(!highPrices.IsActive);
			Debug.Assert(!((Filtering<Order>)highPrices.Source).IsActive);
			
			check(orders, highPrices); // Prints "False"
				  
			// Now we make computations active
			highPrices.For(consumer); // Selecting and Filtering computations is needed for consumer
			
			// Computations is active
			Debug.Assert(highPrices.IsActive);
			Debug.Assert(((Filtering<Order>)highPrices.Source).IsActive);
			
			check(orders, highPrices); // Prints "True"			
			
			Debug.Assert(highPrices is ObservableCollection<decimal>);
			
			check(orders, highPrices); // Prints "True"

			highPrices.CollectionChanged += (sender, eventArgs) =>
			{
				// see the changes (add, remove, replace, move, reset) here			
				check(orders, highPrices); // Prints "True"
			};

			// Start the changing...
			orders.Add(new Order{Num = 8, Price = 30});
			orders.Add(new Order{Num = 9, Price = 10});
			orders[0].Price = 60;
			orders[4].Price = 10;
			orders.Move(5, 1);
			orders[1] = new Order{Num = 10, Price = 17};

			check(orders, highPrices); // Prints "True"
			
			consumer.Dispose(); // the consumer no longer needs its computations
			
			check(orders, highPrices); // Prints "False"
			
			// Computations is not active
			Debug.Assert(!highPrices.IsActive);
			Debug.Assert(!((Filtering<Order>)highPrices.Source).IsActive);			
			
 			Console.ReadLine();		   
		}

		static void check(
			ObservableCollection<Order> orders, 
			Selecting<Order, decimal> expensiveOrders)
		{
			Console.WriteLine(expensiveOrders.SequenceEqual(
				orders.Where(o => o.Price > 25).Select(o => o.Price)));
		}
	}
}

Notice the call to the For extension method. This extension method can be called on all computation instances. If the source of the computation is another computation, that also becomes needed for the consumer.

The OcConsumer class implements the IDisposable interface. When consumer.Dispose() is called, the consumer discards all of its computation. One instance of OcConsumer may need multiple computations. The computation may be needed for multiple instances of OcConsumer. When all instances of OcConsumer abandon the computation, it becomes inactive. The above can be illustrated with a state diagram:

Passing arguments as non-observables and observables

ObservableComputations extension method arguments can be passed in two ways: as non-observables and observables.

Passing arguments as non-observables

using System;
using System.Collections.ObjectModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Person
	{
		public  string Name { get; set; }
	}

	public class LoginManager
	{
		 public Person LoggedInPerson { get; set; }
	}

	class Program
	{
		static void Main(string[] args)
		{
			Person[] allPersons = 
				new []
				{
					new Person(){Name = "Vasiliy"},
					new Person(){Name = "Nikolay"},
					new Person(){Name = "Igor"},
					new Person(){Name = "Aleksandr"},
					new Person(){Name = "Ivan"}
				};

			ObservableCollection<Person> hockeyTeam = 
				new ObservableCollection<Person>(new []
				{
					allPersons[0],
					allPersons[2],
					allPersons[3]
				});

			LoginManager loginManager = new LoginManager();
			loginManager.LoggedInPerson = allPersons[0];

			// We start using ObservableComputations here!
			OcConsumer consumer = new OcConsumer();

			ContainsComputing<Person> isLoggedInPersonHockeyPlayer =
				hockeyTeam.ContainsComputing(loginManager.LoggedInPerson)
				.For(consumer);

			isLoggedInPersonHockeyPlayer.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(ContainsComputing<Person>.Value))
				{
					// see the changes here
				}
			};

			// Start the changing...
			hockeyTeam.RemoveAt(0);		   // 🙂
			hockeyTeam.Add(allPersons[0]);	// 🙂
			loginManager.LoggedInPerson = allPersons[4];  // 🙁!
			
			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

In the code above we compute whether the logged-in person is a hockey player. Expression "loginManager.LoggedInPerson" passed to ContainsComputing method is evaluated by ObservableComputations only once: when ContainsComputing<Person> class is instantiated (when ContainsComputing is called). If LoggedInPerson property changes, that change is not reflected in isLoggedInPersonHockeyPlayer.

Of course, you can use more complex expressions than "loginManager.LoggedInPerson for passing as an argument to any ObservableComputations extension method. As you see passing an argument as non-observable of type T is an ordinary way to pass argument of type T.

Passing argument as observable

In the previous section, we assumed that our application does not support logging out (and subsequent logging in). In other words, the application doesn't treat changes of LoginManager.LoggedInPerson property. Let us add logging out to our application:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.System.Linq.Expressions;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Person
	{
		public  string Name { get; set; }
	}

	public class LoginManager : INotifyPropertyChanged
	{
		private Person _loggedInPerson;

		public Person LoggedInPerson
		{
			get => _loggedInPerson;
			set
			{
				_loggedInPerson = value;
				PropertyChanged?.Invoke(this, 
					new PropertyChangedEventArgs(nameof(LoggedInPerson)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			Person[] allPersons = 
				new []
				{
					new Person(){Name = "Vasiliy"},
					new Person(){Name = "Nikolay"},
					new Person(){Name = "Igor"},
					new Person(){Name = "Aleksandr"},
					new Person(){Name = "Ivan"}
				};

			ObservableCollection<Person> hockeyTeam = 
				new ObservableCollection<Person>(new []
				{
					allPersons[0],
					allPersons[2],
					allPersons[3]
				});

			LoginManager loginManager = new LoginManager();
			loginManager.LoggedInPerson = allPersons[0];

			// We start using ObservableComputations here!	
			OcConsumer consumer = new OcConsumer();
			
			ContainsComputing<Person> isLoggedInPersonHockeyPlayer =
				hockeyTeam.ContainsComputing<Person>(new Computing(
					() => loginManager.LoggedInPerson))
				.For(consumer);

			isLoggedInPersonHockeyPlayer.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(ContainsComputing<Person>.Value))
				{
					// see the changes here
				}
			};

			// Start the changing...
			hockeyTeam.RemoveAt(0);		   // 🙂
			hockeyTeam.Add(allPersons[0]);	// 🙂
			loginManager.LoggedInPerson = allPersons[4];  // 🙂!!!

			Console.ReadLine();
			
			consumer.Dispose();
		}
	}
}

In the code above we pass the argument to the ContainsComputing method as IReadScalar<Person> (not as Person as in the code in the previous section). Computing<Person> implements IReadScalar<Person>. IReadScalar<TValue> was originally mentioned in the "Full list of methods and classes" section. As you see if you want to pass an argument of type T as observable you should perform ordinary argument passing of type IReadScalar<T>. In that case, another overloaded version of ContainsComputing method is used than one in the previous section. It gives us the opportunity to track changes in LoginManager.LoggedInPerson property. Now changes in the LoginManager.LoggedInPerson is reflected in isLoggedInPersonHockeyPlayer. Note than LoginManager class implements INotifyPropertyChanged now.

Сode above can be shortened:

ContainsComputing<Person> isLoggedInPersonHockeyPlayer =
  hockeyTeam.ContainsComputing(() => loginManager.LoggedInPerson);

Using this overloaded version of ContainsComputing method variable loggedInPersonExpression is no longer needed. This overloaded version of ContainsComputing method creates Computing<Person> behind the scene.

Other shortened variant:

ContainsComputing<Person> isLoggedInPersonHockeyPlayer =
	hockeyTeam.ContainsComputing<Person>(
		Expr.Is(() => loginManager.LoggedInPerson).Computing())
	.For(consumer);

Original variant can be useful if you want reuse new Computing(() => loginManager.LoggedInPerson) for other computations than isLoggedInPersonHockeyPlayer. The first shortened variant does not allow that. Shortened variants can be useful for the expression-bodied properties and methods.

Of course, you can use more complex expression than "() => loginManager.LoggedInPerson for passing as an argument to any ObservableComputations extension method.

Passing source collection argument as observable

As you see all calls of LINQ like extension methods generically can be presented as

sourceCollection.ExtensionMethodName(arg1, arg2, ...);

sourceCollection is the first argument in the extension method declaration. So like other arguments that argument can also be passed as non-observable and as observables. Before now we passed the source collections as non-observables (it was the simplest expression consisting of a single variable, of course, we were able to use more complex expressions, but the essence is the same). Now let us try to pass some source collection argument as observable:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq.Expressions;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Person
	{
		public  string Name { get; set; }
	}

	public class LoginManager : INotifyPropertyChanged
	{
		private Person _loggedInPerson;

		public Person LoggedInPerson
		{
			get => _loggedInPerson;
			set
			{
				_loggedInPerson = value;
				PropertyChanged?.Invoke(this, 
					new PropertyChangedEventArgs(nameof(LoggedInPerson)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class HockeyTeamManager : INotifyPropertyChanged
	{
		private ObservableCollection<Person> _hockeyTeamInterested;

		public ObservableCollection<Person> HockeyTeamInterested
		{
			get => _hockeyTeamInterested;
			set
			{
				_hockeyTeamInterested = value;
				PropertyChanged?.Invoke(this, 
					new PropertyChangedEventArgs(nameof(HockeyTeamInterested)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			Person[] allPersons = 
				new []
				{
					new Person(){Name = "Vasiliy"},
					new Person(){Name = "Nikolay"},
					new Person(){Name = "Igor"},
					new Person(){Name = "Aleksandr"},
					new Person(){Name = "Ivan"}
				};

			ObservableCollection<Person> hockeyTeam1 = 
				new ObservableCollection<Person>(new []
				{
					allPersons[0],
					allPersons[2],
					allPersons[3]
				});

			ObservableCollection<Person> hockeyTeam2 = 
				new ObservableCollection<Person>(new []
				{
					allPersons[1],
					allPersons[4]
				});

			LoginManager loginManager = new LoginManager();
			loginManager.LoggedInPerson = allPersons[0];

			HockeyTeamManager hockeyTeamManager = new HockeyTeamManager();
		
			Expression<Func<ObservableCollection<Person>>> hockeyTeamInterestedExpression =
				() => hockeyTeamManager.HockeyTeamInterested;

			// We start using ObservableComputations here!	
			OcConsumer consumer = new OcConsumer();
			
			Computing<ObservableCollection<Person>> hockeyTeamInterestedComputing =
				hockeyTeamInterestedExpression.Computing();

			ContainsComputing<Person> isLoggedInPersonHockeyPlayer =
				hockeyTeamInterestedComputing.ContainsComputing(
					() => loginManager.LoggedInPerson)
				.For(consumer);

			isLoggedInPersonHockeyPlayer.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(ContainsComputing<Person>.Value))
				{
					// see the changes here
				}
			};

			// Start the changing...
			hockeyTeamManager.HockeyTeamInterested = hockeyTeam1;
			hockeyTeamManager.HockeyTeamInterested.RemoveAt(0);		   
			hockeyTeamManager.HockeyTeamInterested.Add(allPersons[0]);  
			loginManager.LoggedInPerson = allPersons[4]; 
			loginManager.LoggedInPerson = allPersons[2];
			hockeyTeamManager.HockeyTeamInterested = hockeyTeam2;		 
			hockeyTeamManager.HockeyTeamInterested.Add(allPersons[2]);  

			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

As in the previous section code above can be shortened:

Expression<Func<ObservableCollection<Person>>> hockeyTeamInterestedExpression =
	() => hockeyTeamManager.HockeyTeamInterested;

ContainsComputing<Person> isLoggedInPersonHockeyPlayer =
	hockeyTeamInterestedExpression
		.ContainsComputing(() => loginManager.LoggedInPerson)
		.For(consumer);

Or:

ContainsComputing<Person> isLoggedInPersonHockeyPlayer =
	Expr.Is(() => hockeyTeamManager.HockeyTeamInterested)
	.ContainsComputing(() => loginManager.LoggedInPerson)
	.For(consumer);

Or:

ContainsComputing<Person> isLoggedInPersonHockeyPlayer =
	new Computing<ObservableCollection<Person>>(
		() => hockeyTeamManager.HockeyTeamInterested)
	.ContainsComputing<Person>(
		() => loginManager.LoggedInPerson)
	.For(consumer);

Or:

ContainsComputing<Person> isLoggedInPersonHockeyPlayer =
	Expr.Is(() => hockeyTeamManager.HockeyTeamInterested).Computing()
	.ContainsComputing(
		() => loginManager.LoggedInPerson)
	.For(consumer);

Of course, you can use more complex expression than "() => hockeyTeamManager.HockeyTeamInterested for passing as an argument to any ObservableComputations extension method.

Non-observable and observable arguments in nested calls

We continue to consider the example from the previous section. We used the following code to track changes in hockeyTeamManager.HockeyTeamInterested:

new Computing<ObservableCollection<Person>>(
	() => hockeyTeamManager.HockeyTeamInterested)

It might seem at first glance that the following code will work and isLoggedInPersonHockeyPlayer will reflect changes of hockeyTeamManager.HockeyTeamInterested:

Computing<bool> isLoggedInPersonHockeyPlayer = new Computing<bool>(() => 
   hockeyTeamManager.HockeyTeamInterested.ContainsComputing(
	() => loginManager.LoggedInPerson).Value);

In that code "hockeyTeamManager.HockeyTeamInterested" is passed to ContainsComputing method as non-observable, and it does not matter that "hockeyTeamManager.HockeyTeamInterested" is part of expression passed to Computing<bool> class constructor, changes of "hockeyTeamManager.HockeyTeamInterested" is not reflected in isLoggedInPersonHockeyPlayer. Non-observable and observable arguments rule is applied in one-way detection: from nested (wrapped) calls to the outer (wrapper) calls. In other words, non-observable and observable arguments rule is always valid, regardless of whether the computation is a root or nested.

Here is another example:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		public event PropertyChangedEventHandler PropertyChanged;

		public int Num {get; set;}

		private string _type;
		public string Type
		{
			get => _type;
			set
			{
				_type = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Type)));
			}
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			ObservableCollection<Order> orders = 
				new ObservableCollection<Order>(new []
				{
					new Order{Num = 1, Type = "VIP"},
					new Order{Num = 2, Type = "Regular"},
					new Order{Num = 3, Type = "VIP"},
					new Order{Num = 4, Type = "VIP"},
					new Order{Num = 5, Type = "NotSpecified"},
					new Order{Num = 6, Type = "Regular"},
					new Order{Num = 7, Type = "Regular"}
				});

			ObservableCollection<string> selectedOrderTypes = new ObservableCollection<string>(new []
				{
					"VIP", "NotSpecified"
				});
				
			OcConsumer consumer = new OcConsumer();

			ObservableCollection<Order> filteredByTypeOrders = 
				orders.Filtering(o => 
					selectedOrderTypes.ContainsComputing(() => o.Type).Value)
				.For(consumer);
			

			filteredByTypeOrders.CollectionChanged += (sender, eventArgs) =>
			{
				// see the changes (add, remove, replace, move, reset) here			
			};

			// Start the changing...
			orders.Add(new Order{Num = 8, Type = "VIP"});
			orders.Add(new Order{Num = 9, Type = "NotSpecified"});
			orders[4].Type = "Regular";
			orders.Move(4, 1);
			orders[0] = new Order{Num = 10, Type = "Regular"};
			selectedOrderTypes.Remove("NotSpecified");

			Console.ReadLine();
			
			consumer.Dispose();
		}
	}
}

In the code above we have created "filteredByTypeOrders" computation that reflects changes in orders, selectedOrderTypes collections, and in the Order.Type property. Take attention to the argument passed to ContainsComputing. Following code will not reflect changes in the Order.Type property:

ObservableCollection<Order> filteredByTypeOrders =  orders.Filtering(o => 
   selectedOrderTypes.ContainsComputing(o.Type).Value);

Computation result change request handlers

The only way to modify the result of a computation is to modify source data. Неre is the code:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		public int Num {get; set;}

		private string _manager;
		public string Manager
		{
			get => _manager;
			set
			{
				_manager = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Manager)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;

	}

	class Program
	{
		static void Main(string[] args)
		{
			ObservableCollection<Order> orders = 
				new ObservableCollection<Order>(new []
				{
					new Order{Num = 1, Manager = "Stepan"},
					new Order{Num = 2, Manager = "Aleksey"},
					new Order{Num = 3, Manager = "Aleksey"},
					new Order{Num = 4, Manager = "Oleg"},
					new Order{Num = 5, Manager = "Stepan"},
					new Order{Num = 6, Manager = "Oleg"},
					new Order{Num = 7, Manager = "Aleksey"}
				});

			OcConsumer consumer = new OcConsumer();

			Filtering<Order> stepansOrders =  
				orders.Filtering(o => 
					o.Manager == "Stepan")
				.For(consumer);
			
			stepansOrders.InsertItemRequestHandler = (i, order) =>
			{
				orders.Add(order);
				order.Manager = "Stepan";
			};

			Order newOrder = new Order(){Num = 8};
			stepansOrders.Add(newOrder);
			Debug.Assert(stepansOrders.Contains(newOrder));

			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

In the code above we created stepansOrders (Stepan's orders) computation. We set the delegate to stepansOrders.InsertItemRequestHandler property to define how to modify orders collection and order to be inserted so what one is included in stepansOrders computation.

Note that Add method is a member of ICollection<T> interface.

This feature can be used if you pass stepansOrders to the code abstracted from what is stepansOrders: computation or ordinary collection. That code only knows stepansOrders implements ICollection<T> interface and sometimes wants to add orders to stepansOrders. Such a code is for example two way binding in WPF or binding to ItemsSource in the DataGrid.

Properties similar to InsertItemRequestHandler exist for all other operations (remove, set, move, clear). All the properties have postfix "RequestHandler".

Change handling by a user

Change handling in ObservableCollection<T>

Sometimes it becomes necessary to perform some actions

  • with elements added to the collection
  • with items to be removed from the collection
  • elements moved within the collection

Of course, you can process all the current elements in the collection, then subscribe to the CollectionChanged event, but the ObservableComputations library contains a simpler and more effective tool.

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Client : INotifyPropertyChanged
	{
		public string Name { get; set; }

		private bool _online;

		public bool Online
		{
			get => _online;
			set
			{
				_online = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Online)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class NetworkChannel : IDisposable
	{
		public NetworkChannel(string clientName)
		{
			ClientName = clientName;
			Console.WriteLine($"NetworkChannel to {ClientName} has been created");
		}

		public string ClientName { get; set; }

		public void Dispose()
		{
			Console.WriteLine($"NetworkChannel to {ClientName} has been disposed");
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			ObservableCollection<Client> clients = new ObservableCollection<Client>(new Client[]
			{
				new Client(){Name  = "Sergey", Online = false},
				new Client(){Name  = "Evgeney", Online = true},
				new Client(){Name  = "Anatoley", Online = false},
				new Client(){Name  = "Timofey", Online = true}
			});
			
			OcConsumer consumer = new OcConsumer();

			Filtering<Client> onlineClients = clients.Filtering(c => c.Online);

			onlineClients.CollectionItemProcessing(
				(newClient, collectionProcessing) => 
					new NetworkChannel(newClient.Name),
				(oldClient, collectionProcessing, networkChannel) => 
					networkChannel.Dispose())
			.For(consumer);
					
			clients[2].Online = true;
			clients.RemoveAt(1);

			consumer.Dispose();

			Console.ReadLine();		  
		}
	}
}

Delegate passed to the newItemProcessor parameter is called

  • when activating instance of CollectionProcessing<TSourceItem, TReturnValue> class (if the source collection (onlineClients) contains elements at the time of activation),
  • when adding an item to the source collection (onlineClients),
  • when replacing an item in the source collection (setting the collection item by index),
  • when resetting the source collection and it contains items after reset,
  • when source collection is passed as a scalar (IReadScalar<TValue>), and its value changes to the collection that contains the elements.

The delegate passed to the oldItemProcessor parameter is called

It is also possible to pass moveItemProcessor delegate to handle the event of element move in the source collection.

The CollectionItemProcessing method processes items in a collection one at a time. The CollectionItemsProcessing method allows you to process multiple collection items at once. Multiple items are processed at activation, deactivation and at [Reset](https://docs.microsoft.com/en-us/dotnet/api/system. collections.specialized.notifycollectionchangedaction? view = net-5.0) ([Clear](https://docs.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.collection-1.clear?view=net -5.0)) source collection. The CollectionItemsProcessing method is not convenient for processing changes associated with a single item in the source collection.

There is also an overloaded version of the CollectionItemProcessing (CollectionItemsProcessing) method, which accepts newItemProcessor (newItemsProcessor) delegate that returns an empty value (void).

Handling changes in IReadScalar<TValue>

IReadScalar<TValue> is mentioned for the first time here. You can handle changes to the Value property by subscribing to the PropertyChanged event, but ObservableComputations allows you to process changes in IReadScalar<TValue> easier and more efficiently (similar to processing changes in ObservableCollection<T>):

using System;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Client : INotifyPropertyChanged
	{
		private NetworkChannel _networkChannel;

		public NetworkChannel NetworkChannel
		{
			get => _networkChannel;
			set
			{
				_networkChannel = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NetworkChannel)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class NetworkChannel : IDisposable
	{
		public NetworkChannel(int num)
		{
			Num = num;
			
		}

		public int Num { get; set; }

		public void Open()
		{
			Console.WriteLine($"NetworkChannel #{Num} has been opened");
		}

		public void Dispose()
		{
			Console.WriteLine($"NetworkChannel #{Num} has been disposed");
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			var networkChannel  = new NetworkChannel(1);
			Client client = new Client() {NetworkChannel = networkChannel};

			OcConsumer consumer = new OcConsumer();

			Computing<NetworkChannel> networkChannelComputing 
				= new Computing<NetworkChannel>(() => client.NetworkChannel);

			networkChannelComputing.ScalarProcessing(
				(newNetworkChannel, scalarProcessing) => 
					newNetworkChannel.Open(),
				(oldNetworkChannel, scalarProcessing) => 
					oldNetworkChannel.Dispose())
			.For(consumer);

			client.NetworkChannel = new NetworkChannel(2);
			client.NetworkChannel = new NetworkChannel(3);
		   
			consumer.Dispose();

			Console.ReadLine();			 
		}
	}
}

There is also an overloaded version of the ScalarProcessing method that accepts a newValueProcessor delegate that returns a non-void value.

Disposing

If items in your collection implement IDisposable you may need to call Dispose method for each item leaving the collection (Remove, Replace, Clear). You may use CollectionProcessing to achieve this as we did in the previous section. Another variant is to use CollectionDisposing method:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Client : INotifyPropertyChanged
	{
		public string Name { get; set; }

		private bool _online;

		public bool Online
		{
			get => _online;
			set
			{
				_online = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Online)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class NetworkChannel : IDisposable
	{
		public NetworkChannel(string clientName)
		{
			ClientName = clientName;
			Console.WriteLine($"NetworkChannel to {ClientName} has been created");
		}

		public string ClientName { get; set; }

		public void Dispose()
		{
			Console.WriteLine($"NetworkChannel to {ClientName} has been disposed");
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			ObservableCollection<Client> clients = new ObservableCollection<Client>(new Client[]
			{
				new Client(){Name  = "Sergey", Online = false},
				new Client(){Name  = "Evgeney", Online = true},
				new Client(){Name  = "Anatoley", Online = false},
				new Client(){Name  = "Timofey", Online = true}
			});
			
			OcConsumer consumer = new OcConsumer();

			Filtering<Client> onlineClients = clients.Filtering(c => c.Online);

			onlineClients
			.CollectionItemProcessing(
				(newClient, collectionProcessing) => 
					new NetworkChannel(newClient.Name))
			.CollectionDisposing()
			.For(consumer);
					
			clients[2].Online = true;
			clients.RemoveAt(1);

			consumer.Dispose();

			Console.ReadLine();		  
		}
	}
}

ScalarDisposing extension method allows you to dispose of old values of IReadScalar:

using System;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Client : INotifyPropertyChanged
	{
		private NetworkChannel _networkChannel;

		public NetworkChannel NetworkChannel
		{
			get => _networkChannel;
			set
			{
				_networkChannel = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NetworkChannel)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class NetworkChannel : IDisposable
	{
		public NetworkChannel(int num)
		{
			Num = num;
			
		}

		public int Num { get; set; }

		public void Open()
		{
			Console.WriteLine($"NetworkChannel #{Num} has been opened");
		}

		public void Dispose()
		{
			Console.WriteLine($"NetworkChannel #{Num} has been disposed");
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			var networkChannel  = new NetworkChannel(1);
			Client client = new Client() {NetworkChannel = networkChannel};

			Computing<NetworkChannel> networkChannelComputing 
				= new Computing<NetworkChannel>(() => client.NetworkChannel);

			OcConsumer consumer = new OcConsumer();

			networkChannelComputing.ScalarProcessing(
				(newNetworkChannel, scalarProcessing) => 
					newNetworkChannel.Open())
			.ScalarDisposing()
			.For(consumer);

			client.NetworkChannel = new NetworkChannel(2);
			client.NetworkChannel = new NetworkChannel(3);

			consumer.Dispose();

			Console.ReadLine();
		}
	}
}

Overlapped changes processing

When the handler of PropetyChanged or CollectionChanged event of computation is being executed, that computation is processing some change of source and is in an inconsistent state (has IsConsistent == false). All changes of sources made at that time (overlapping changes) will be deferred until the computation completes the processing of the original source change. Consider the following code:

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public enum RelationType { Parent, Child }

	public struct Relation
	{
		public string From {get; set;}
		public string To {get; set;}
		public RelationType Type {get; set;}

		public Relation CorrespondingRelation => 
			new Relation(){
				From = this.To,
				To = this.From,
				Type = this.Type == RelationType.Child 
					? RelationType.Parent 
					: RelationType.Child};
	}

	class Program
	{
		static void Main(string[] args)
		{
			ObservableCollection<Relation> relations = 
				new ObservableCollection<Relation>(new []
				{
					new Relation{From = "Valentin", To = "Filipp", Type = RelationType.Child},
					new Relation{From = "Filipp", To = "Valentin", Type = RelationType.Parent},

					new Relation{From = "Olga", To = "Evgeny", Type = RelationType.Child},
					new Relation{From = "Evgeny", To = "Olga", Type = RelationType.Parent}
				});

			OcConsumer consumer = new OcConsumer();

			Ordering<Relation, string> orderedRelations = 
				relations.Ordering(r => r.From)
				.For(consumer);

			orderedRelations.CollectionChanged += (sender, eventArgs) =>
			{
				switch (eventArgs.Action)
				{
					case NotifyCollectionChangedAction.Add:
						Relation newRelation = (Relation) eventArgs.NewItems[0];
						if (relations.Contains(newRelation.CorrespondingRelation))
							return;

						relations.Add(newRelation.CorrespondingRelation); // this change
						// was not reflected in orderedRelations for now
						// (it's processing was deferred and will be done latter) 
						// so following assertion is passes
						Debug.Assert(!orderedRelations.Contains(newRelation.CorrespondingRelation));

						// It's because orderedRelations is processing change "relations.Add(relation);" now and cannot process other changes
						// State of orderedRelations is inconsistent:
						Debug.Assert(!orderedRelations.IsConsistent);
						break;
					case NotifyCollectionChangedAction.Remove:
						//...
						break;
				}
			};

			Relation relation = new Relation{From = "Arseny", To = "Dmitry", Type = RelationType.Parent};
			relations.Add(relation); 
			// at this point orderedRelations has completed processing of change "relations.Add(relation);". 
			// All deferred changes have been processed also 
			// so following assertion is passes
			Debug.Assert(orderedRelations.Contains(relation.CorrespondingRelation));

			// State of orderedRelations is consistent:
			Debug.Assert(orderedRelations.IsConsistent);

			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

In the code above we have a collection of relations: relations. That collection has redundancy: if the collection contains relation A to B as a parent, it must contain corresponding relation: B to A as a child, and vise versa. Also, we have the computed collection of ordered relations: orderedRelations. Our task is to support the integrity of relations collection: if someone changes it, we have to react, so the collection restores integrity. Imagine that the only way to do it is to subscribe to CollectionChanged event of orderedRelations collection (for some reason we cannot subscribe to CollectionChanged event of relations collection). In the code above we consider only one type of change: Add.

Debugging

User code

Use code includes:

  • Selectors are expressions that are passed as an argument to the following extension methods: Selecting, SelectingMany, Grouping, GroupJoining, Dictionaring, Hashing, Ordering, ThenOrdering, PredicateGroupJoining.

  • Predicates are expressions that are passed as an argument to Filtering extension method.

  • Aggregation functions are delegates that are passed as an argument to Aggregating extension method.

  • Arbitrary expressions are expressions that are passed as an argument to Computing and Using extension methods.

  • Computation result change request handlers was described here.

  • Code called using the methods OcDispatcher.Invoke*.

Here is the code illustrating debugging of arbitrary expressions (other types of code can be debugged in the same way):

using System;
using System.ComponentModel;
using System.Threading;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class ValueProvider : INotifyPropertyChanged
	{
		private int _value;

		public int Value
		{
			get => _value;
			set
			{
				_value = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}
	class Program
	{
		static void Main(string[] args)
		{
			OcConfiguration.SaveInstantiationStackTrace = true;
			OcConfiguration.TrackComputingsExecutingUserCode = true;

			ValueProvider valueProvider = new ValueProvider(){Value = 2};

			OcConsumer consumer = new OcConsumer();

			Computing<decimal> computing1 = 
				new Computing<decimal>(() => 1 / valueProvider.Value)
				.For(consumer);

			Computing<decimal> computing2 = 
				new Computing<decimal>(() => 1 / (valueProvider.Value - 1))
				.For(consumer);;

			try
			{
				valueProvider.Value = new Random().Next(0, 1);
			}
			catch (DivideByZeroException exception)
			{
				Console.WriteLine($"Exception stacktrace:\n{exception.StackTrace}");

				IComputing computing = StaticInfo.ComputingsExecutingUserCode[Thread.CurrentThread.ManagedThreadId];
				Console.WriteLine($"\nComputing which caused the exception has been instantiated by the following stacktrace :\n{computing.InstantiationStackTrace}");
								Console.WriteLine($"\nSender of event now processing is :\n{computing.HandledEventSender.ToStringSafe()}");
				Console.WriteLine($"\nArgs for the event that is currently being processed is :\n{computing.HandledEventArgs.ToStringAlt()}");
			}

			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

As you see exception.StackTrace points to line caused the exception: valueProvider.Value = new Random().Next(0, 1);. That line doesn't point us to computation which caused the exception: computing1 or computing2. To determine the computation which caused the exception we should look at StaticInfo.ComputingsExecutingUserCode[Thread.CurrentThread.ManagedThreadId].InstantiatingStackTrace property. That property contains a stack trace of instantiating of the computation.

By default, ObservableComputations doesn't save stack traces of instantiating of computations for performance reasons. To save those stack traces use OcConfiguration.SaveInstantiationStackTrace property.

By default, ObservableComputations doesn't track computations executing user code for performance reasons. To track computations executing user code use OcConfiguration.TrackComputingsExecutingUserCode property. If the user code was called from the user code of another computation, then StaticInfo.ComputingsExecutingUserCode[Thread.CurrentThread].UserCodeIsCalledFrom will point to that computation.

All unhandled exceptions thrown in the user code are fatal, as the internal state of the computations becomes damaged. Pay attention to null checks.

User code in background threads

Work with computations in background threads is described here.

using System;
using System.ComponentModel;
using System.Threading;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class ValueProvider : IReadScalar<int>
	{
		private int _value;

		public int Value
		{
			get => _value;
			set
			{
				_value = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}
	
	class Program
	{
		static void Main(string[] args)
		{
			OcConfiguration.SaveInstantiationStackTrace = true;
			OcConfiguration.TrackComputingsExecutingUserCode = true;
			OcConfiguration.SaveOcDispatcherInvocationInstantiationStackTrace = true;
			OcConfiguration.SaveOcDispatcherInvocationExecutionStackTrace = true;

			ValueProvider valueProvider = new ValueProvider(){Value = 2};
			
			OcDispatcher ocDispatcher = new OcDispatcher();

			System.AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
			{
				Thread.CurrentThread.IsBackground = true;

				Invocation currentInvocation = StaticInfo.OcDispatchers[ocDispatcher.ManagedThreadId].CurrentInvocation;
				Console.WriteLine($"Exception stacktrace:\n{currentInvocation.InstantiationStackTrace}");
				Console.WriteLine($"\nComputing which caused the exception has been instantiated by the following stacktrace :\n{StaticInfo.ComputingsExecutingUserCode[Thread.CurrentThread.ManagedThreadId].InstantiationStackTrace}");
				Console.WriteLine($"\nDispatch computing which caused the exception has been instantiated by the following stacktrace :\n{((IComputing)currentInvocation.Context).InstantiationStackTrace}");

				while (true)
					Thread.Sleep(TimeSpan.FromHours(1));
			};

			OcConsumer consumer = new OcConsumer();

			ScalarDispatching<int> valueProviderDispatching = 
				valueProvider.ScalarDispatching(ocDispatcher)
				.For(consumer);

			ocDispatcher.Pass();

			Computing<decimal> computing1 = 
				new Computing<decimal>(() => 1 / valueProviderDispatching.Value)
				.For(consumer);

			Computing<decimal> computing2 = 
				new Computing<decimal>(() => 1 / (valueProviderDispatching.Value - 1))
				.For(consumer);

			valueProvider.Value = new Random().Next(0, 2);

			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

This example is similar to the previous one, except

  • Properties that contain exception information
  • Setting configuration parameters OcConfiguration.SaveOcDispatcherInvocationInstantiationStackTrace and OcConfiguration.TrackOcDispatcherInvocations

OcConfiguration.SaveOcDispatcherInvocationExecutionStackTrace, Invocation.ExecutionStackTrace, Invocation.Executor и Invocation.Parent properties can be usefull, when you call OcDispatcher.ExecuteOtherInvocations or OcDispatcher.Invoke* methods in the OcDispatcher thread.

Additional events for changes handling: PreCollectionChanged, PreValueChanged, PostCollectionChanged, PostValueChanged

using System;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		private double _price;
		public double Price
		{
			get => _price;
			set
			{
				_price = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Price)));
			}
		}

		private bool _discount;
		public bool Discount
		{
			get => _discount;
			set
			{
				_discount = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Discount)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			Console.OutputEncoding = System.Text.Encoding.UTF8;

			Order order = new Order(){Price = 100};

			OcConsumer consumer = new OcConsumer();

			Computing<string> messageForUser = null;

			Computing<double> priceDiscounted = 
				new Computing<double>(() => order.Discount 
					? order.Price - order.Price * 0.1 
					: order.Price)
				.For(consumer);

			priceDiscounted.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(Computing<double>.Value))
					Console.WriteLine(messageForUser.Value);
			};

			messageForUser = 
				new Computing<string>(() => order.Price > priceDiscounted.Value
					? $"Your order price is ₽{order.Price}. You have a discount! Therefore your price is ₽{priceDiscounted.Value}!"
					: $"Your order price is ₽{order.Price}")
				.For(consumer);

			order.Discount = true;

			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

Code above has the following output:

Your order price is ₽100

Although we could expect:

Your order price is ₽100. You have a discount! Therefore your price is ₽90!

Why? We subscribe to priceDiscounted.PropertyChanged before messageForUser does it. Event handlers are invoked in the order of subscriptions (it is an implementation detail of .NET). So we read messageForUser.Value before messageForUser handles the change of order.Discount.

Here is the fixed code:

using System;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		private double _price;
		public double Price
		{
			get => _price;
			set
			{
				_price = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Price)));
			}
		}

		private bool _discount;
		public bool Discount
		{
			get => _discount;
			set
			{
				_discount = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Discount)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			Console.OutputEncoding = System.Text.Encoding.UTF8;

			Order order = new Order(){Price = 100};

			OcConsumer consumer = new OcConsumer();

			Computing<string> messageForUser = null;

			Computing<double> priceDiscounted = 
				new Computing<double>(() => order.Discount 
					? order.Price - order.Price * 0.1 
					: order.Price)
				.For(consumer);

			// HERE IS THE FIX!
			priceDiscounted.PostValueChanged += (sender, eventArgs) =>
			{
				Console.WriteLine(messageForUser.Value);
			};

			messageForUser = 
				new Computing<string>(() => order.Price > priceDiscounted.Value
					? $"Your order price is ₽{order.Price}. You have a discount! Therefore your price is ₽{priceDiscounted.Value}!"
					: $"Your order price is ₽{order.Price}")
				.For(consumer);

			order.Discount = true;

			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

Instead of priceDiscounted.PropertyChanged we subscribe to priceDiscounted.PostValueChanged. That event is raised after PropertyChanged, so we can sure: all the dependent computations have refreshed their values. PostValueChanged is declared in ScalarComputing<TValue>. Computing<string> inherits ScalarComputing<TValue>. ScalarComputing<TValue> is mentioned here for the first time. ScalarComputing<TValue> contains PreValueChanged event. That event allows you see the state of the all computations before a change.

CollectionComputing<TItem> contains PreCollectionChanged and PostCollectionChanged events. CollectionComputing<TItem> is mentioned here for the first time. If you want handle collection change of your collection that implements INotifyCollectionChanged (not of computed collection (for example ObservableCollection<TItem>) and that handle reads dependent computations you may use ObservableCollectionExtended<TItem>. That class inherits ObservableCollection<TItem> and contains PreCollectionChanged and PostCollectionChanged events. Also you can use Extending extension method. That method creates ObservableCollectionExtended<TItem> from INotifyCollectionChanged.

Multithreading

Thread safety

CollectionComputing<TSourceItem> and ScalarComputing<TSourceItem>

  • supports multiple reader threads simultaneously, as long there are no modifications made by the writer thread. Exclusion: ConcurrentDictionaring computation, which supports simultaneous multiple reader threads and single writer thread.
  • do not support simultaneous modifications by multiple writer threads.

The computations are modified by the writer thread

Loading source data in a background thread

Code of the window of the WPF application:

<Window
	x:Class="ObservableComputationsExample.MainWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:local="clr-namespace:ObservableComputationsExample"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	x:Name="uc_this"
	Title="ObservableComputationsExample"
	Width="800"
	Height="450"
	mc:Ignorable="d"
	Closed="mainWindow_OnClosed">
	<Grid>
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="*" />
			<ColumnDefinition Width="*" />
		</Grid.ColumnDefinitions>
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="*" />
		</Grid.RowDefinitions>

		<Label
			x:Name="uc_LoadingIndicator"
			Grid.Row="0"
			Grid.Column="0"
			Grid.ColumnSpan="2"
			HorizontalAlignment="Left">
			Loading source data...
		</Label>

		<Label
			Grid.Row="1"
			Grid.Column="0"
			FontWeight="Bold">
			Unpaid orders
		</Label>
		<ListBox
			Grid.Row="2"
			Grid.Column="0"
			DisplayMemberPath="Num"
			ItemsSource="{Binding UnpaidOrders, ElementName=uc_this}" />

		<Label
			Grid.Row="1"
			Grid.Column="1"
			FontWeight="Bold">
			Paid orders
		</Label>
		<ListBox
			Grid.Row="2"
			Grid.Column="1"
			DisplayMemberPath="Num"
			ItemsSource="{Binding PaidOrders, ElementName=uc_this}" />
	</Grid>
</Window>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using ObservableComputations;

namespace ObservableComputationsExample
{
	public partial class MainWindow : Window
	{
		public ObservableCollection<Order> Orders { get; }
		public ObservableCollection<Order> PaidOrders { get; }
		public ObservableCollection<Order> UnpaidOrders { get; }
		private readonly OcConsumer _ocConsumer = new OcConsumer();

		public MainWindow()
		{
			Orders = new ObservableCollection<Order>();
			fillOrdersFromDb();
			
			PaidOrders = Orders.Filtering(o => o.Paid).For(_ocConsumer);
			UnpaidOrders = Orders.Filtering(o => !o.Paid).For(_ocConsumer);

			InitializeComponent();
		}

		private void fillOrdersFromDb()
		{
			Thread thread = new Thread(() =>
			{
				Thread.Sleep(1000); // accessing DB
				Random random = new Random();
				for (int i = 0; i < 5000; i++)
				{
					Order order = new Order(i);
					order.Paid = Convert.ToBoolean(random.Next(0, 3));
					this.Dispatcher.Invoke(() => Orders.Add(order), DispatcherPriority.Background);
				}

				this.Dispatcher.Invoke(
					() => uc_LoadingIndicator.Visibility = Visibility.Hidden, 
					DispatcherPriority.Background);
			});

			thread.Start();
		}

		private void mainWindow_OnClosed(object sender, EventArgs e)
		{
			_ocConsumer.Dispose();
		}
	}

	public class Order : INotifyPropertyChanged
	{
		public Order(int num)
		{
			Num = num;
		}

		public int Num { get; }

		private bool _paid;
		public bool Paid
		{
			get => _paid;
			set
			{
				_paid = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paid)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}
}

In this example, we show the user form without waiting for the data to load from the database to finish. While loading, the form is rendered and the user gets acquainted with its contents. Note that the source code loading code is abstracted from computations over them (PaidOrders and UnpaidOrders).

Performing computations in a background thread

In the previous example, only data from the database was loaded in the background thread. The computations (PaidOrders and UnpaidOrders) were performed in the main thread (UI thread). Sometimes it is necessary to perform computations in a background thread, and in the main thread to get only the final computation results (XAML is the same as in the previous example):

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using ObservableComputations;

namespace ObservableComputationsExample
{
	public partial class MainWindow : Window
	{
		public ObservableCollection<Order> Orders { get; }
		public ObservableCollection<Order> PaidOrders { get; }
		public ObservableCollection<Order> UnpaidOrders { get; }
		private readonly OcConsumer _consumer = new OcConsumer();

		// OcDispatcher for computations in the background thread
		ObservableComputations.OcDispatcher _ocDispatcher = new ObservableComputations.OcDispatcher();

		public MainWindow()
		{
			Orders = new ObservableCollection<Order>();

			WpfOcDispatcher wpfOcDispatcher = new WpfOcDispatcher(this.Dispatcher);

			fillOrdersFromDb();

			PaidOrders = 
				Orders.CollectionDispatching(_ocDispatcher) // direct the computation to the background thread
				.Filtering(o => o.Paid)
				.CollectionDispatching(wpfOcDispatcher, _ocDispatcher, (int)DispatcherPriority.Background) // return the computation to the main thread from the background one
				.For(_consumer);

			UnpaidOrders = 
				Orders
				.Filtering(o => !o.Paid)
				.For(_consumer);

			InitializeComponent();
		}

		private void fillOrdersFromDb()
		{
			Thread.Sleep(1000); // accessing DB
			Random random = new Random();
			for (int i = 0; i < 10000; i++)
			{
				Order order = new Order(i);
				order.Paid = Convert.ToBoolean(random.Next(0, 3));
				this.Dispatcher.Invoke(() => Orders.Add(order), DispatcherPriority.Background);
			}
		}

		private void mainWindow_OnClosed(object sender, EventArgs e)
		{
			_ocDispatcher.Dispose();
			_consumer.Dispose();
		}
	}

	public class Order : INotifyPropertyChanged
	{
		public Order(int num)
		{
			Num = num;
		}

		public int Num { get; }

		private bool _paid;
		public bool Paid
		{
			get => _paid;
			set
			{
				_paid = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paid)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class WpfOcDispatcher : IOcDispatcher
	{
		private Dispatcher _dispatcher;

		public WpfOcDispatcher(Dispatcher dispatcher)
		{
			_dispatcher = dispatcher;
		}

		#region Implementation of IOcDispatcher

		public void Invoke(Action action, int priority, object parameter, object context)
		{
			_dispatcher.BeginInvoke(action, (DispatcherPriority)priority);
		}

		#endregion
	}
}

In this example, we load data from the database in the main thread, but filtering the source collection Orders to receive paid orders (PaidOrders) is performed in the background thread. ObservableComputations.OcDispatcher class is very similar to the class System.Windows.Threading.Dispatcher. ObservableComputations.OcDispatcher class is associated with a single thread. You can execute delegates in this thread by calling ObservableComputations.OcDispatcher.Invoke* methods. The CollectionDispatching method redirects all changes of the source collection to the target OcDispatcher thread (distinationOcDispatcher parameter). When the CollectionDispatching method is called, the source collection is enumerated (Orders or Orders.CollectionDispatching(_ocDispatcher).Filtering (o => o.Paid)) and its CollectionChanged event is subscribed. While that enumeration the source collection should not be changed. When calling .CollectionDispatching(_ocDispatcher), the collection Orders do not change. When calling CollectionDispatching(wpfOcDispatcher, _ocDispatcher) collection Orders.CollectionDispatching(_ocDispatcher).Filtering (o => o.Paid) may change in the _ocDispatcher thread, but since we pass _ocDispatcher to the sourceOcDispatcher parameter, the enumeration of the source collection and subscription to its CollectionChanged event occurs in the thread of _ocDispatcher, which guarantees that there are no changes to the source collection during enumeration. Since when calling CollectionDispatching(_ocDispatcher), the Orders collection does not change, then passing wpfOcDispatcher to the sourceOcDispatcher parameter makes no sense, especially since at the time of calling CollectionDispatching(_ocDispatcher) we are in the thread of wpfOcDispatcher. In most cases, unnecessarily passing the sourceDispatcher parameter will not result in a loss of workability, unless the performance is slightly affected.

Note how DispatcherPriority.Background is passed through destinationOcDispatcherPriority parameter of CollectionDispatching extension method to WpfOcDispatcher.Invoke method.

Note the need to call _ocDispatcher.Dispose(). The above example is not the only design option. Here is another option (XAML is the same as in the previous example):

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using ObservableComputations;

namespace ObservableComputationsExample
{
   public partial class MainWindow : Window
   {
   	public ObservableCollection<Order> Orders { get; }
   	public ObservableCollection<Order> PaidOrders { get; }
   	public ObservableCollection<Order> UnpaidOrders { get; }
   	private readonly OcConsumer _consumer = new OcConsumer();
   	WpfOcDispatcher _wpfOcDispatcher;
   	
   	// OcDispatcher for computations in the background thread
   	OcDispatcher _ocDispatcher = new OcDispatcher();

   	public MainWindow()
   	{
   		_wpfOcDispatcher = new WpfOcDispatcher(this.Dispatcher);
   		
   		Orders = new ObservableCollection<Order>();

   		fillOrdersFromDb();

   		PaidOrders = 
   			Orders
   			.Filtering(o => o.Paid)
   			.CollectionDispatching(_wpfOcDispatcher, _ocDispatcher, (int)DispatcherPriority.Background) // return the computation to the main thread from the background one
   			.For(_consumer);

   		UnpaidOrders = 
   			Orders
   			.Filtering(o => !o.Paid)
   			.CollectionDispatching(_wpfOcDispatcher, _ocDispatcher, (int)DispatcherPriority.Background) // return the computation to the main thread from the background one
   			.For(_consumer);

   		InitializeComponent();
   	}

   	private void fillOrdersFromDb()
   	{
   		Thread thread = new Thread(() =>
   		{
   			Thread.Sleep(1000); // accessing DB
   			Random random = new Random();
   			for (int i = 0; i < 5000; i++)
   			{
   				Order order = new Order(i);
   				order.Paid = Convert.ToBoolean(random.Next(0, 3));
   				_ocDispatcher.Invoke(() => Orders.Add(order));
   			}

   			this.Dispatcher.Invoke(
   				() => uc_LoadingIndicator.Visibility = Visibility.Hidden, 
   				DispatcherPriority.Background);
   		});

   		thread.Start();
   	}

   	private void mainWindow_OnClosed(object sender, EventArgs e)
   	{
   		_ocDispatcher.Dispose();
   		_consumer.Dispose();
   	}		
   }

   public class Order : INotifyPropertyChanged
   {
   	public Order(int num)
   	{
   		Num = num;
   	}

   	public int Num { get; }

   	private bool _paid;
   	public bool Paid
   	{
   		get => _paid;
   		set
   		{
   			_paid = value;
   			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paid)));
   		}
   	}

   	public event PropertyChangedEventHandler PropertyChanged;
   }

   public class WpfOcDispatcher : IOcDispatcher
   {
   	private Dispatcher _dispatcher;

   	public WpfOcDispatcher(Dispatcher dispatcher)
   	{
   		_dispatcher = dispatcher;
   	}

   	#region Implementation of IOcDispatcher

   	public void Invoke(Action action, int priority, object parameter, object context)
   	{
   		_dispatcher.BeginInvoke(action, (DispatcherPriority)priority);
   	}

   	#endregion
   }
}

And one more:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using ObservableComputations;

namespace ObservableComputationsExample
{
	public partial class MainWindow : Window
	{
		public ObservableCollection<Order> Orders { get; }
		public ObservableCollection<Order> PaidOrders { get; }
		public ObservableCollection<Order> UnpaidOrders { get; }
		private readonly OcConsumer _consumer = new OcConsumer();

		WpfOcDispatcher _wpfOcDispatcher;

		public MainWindow()
		{
			_wpfOcDispatcher = new WpfOcDispatcher(this.Dispatcher);
			
			Orders = new ObservableCollection<Order>();

			PaidOrders = 
				Orders
				.Filtering(o => o.Paid)
				.CollectionDispatching(_wpfOcDispatcher, (int)DispatcherPriority.Background) // direct the computation to the main thread
				.For(_consumer);

			UnpaidOrders = 
				Orders
				.Filtering(o => !o.Paid)
				.CollectionDispatching(_wpfOcDispatcher, (int)DispatcherPriority.Background) // direct the computation to the main thread
				.For(_consumer);

			InitializeComponent();

			fillOrdersFromDb();
		}

		private void fillOrdersFromDb()
		{
			Thread thread = new Thread(() =>
			{
				Thread.Sleep(1000); // accessing DB
				Random random = new Random();
				for (int i = 0; i < 5000; i++)
				{
					Order order = new Order(i);
					order.Paid = Convert.ToBoolean(random.Next(0, 3));
					Orders.Add(order);
				}

				this.Dispatcher.Invoke(
					() => uc_LoadingIndicator.Visibility = Visibility.Hidden, 
					DispatcherPriority.Background);
			});

			thread.Start();
		}

		private void mainWindow_OnClosed(object sender, EventArgs e)
		{
			_consumer.Dispose();
		}
	}

	public class Order : INotifyPropertyChanged
	{
		public Order(int num)
		{
			Num = num;
		}

		public int Num { get; }

		private bool _paid;
		public bool Paid
		{
			get => _paid;
			set
			{
				_paid = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paid)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class WpfOcDispatcher : IOcDispatcher
	{
		private Dispatcher _dispatcher;

		public WpfOcDispatcher(Dispatcher dispatcher)
		{
			_dispatcher = dispatcher;
		}

		#region Implementation of IOcDispatcher

		public void Invoke(Action action, int priority, object parameter, object context)
		{
			_dispatcher.BeginInvoke(action, (DispatcherPriority)priority);
		}

		#endregion
	}
}

Property dispatching

In the previous examples, we saw how collections are dispatched using the CollectionDispatching extension method. But you may also need to dispatch properties:

<Window
	x:Class="ObservableComputationsExample.MainWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:local="clr-namespace:ObservableComputationsExample"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	x:Name="uc_this"
	Title="ObservableComputationsExample"
	Width="800"
	Height="450"
	mc:Ignorable="d"
	Closed="mainWindow_OnClosed">
	<Grid>
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="*" />
			<ColumnDefinition Width="*" />
		</Grid.ColumnDefinitions>
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition Height="*" />
		</Grid.RowDefinitions>

		<Label
			x:Name="uc_LoadingIndicator"
			Grid.Row="0"
			Grid.Column="0"
			Grid.ColumnSpan="2"
			HorizontalAlignment="Left">
			Loading source data...
		</Label>

		<Label
			Grid.Row="1"
			Grid.Column="0"
			FontWeight="Bold">
			Unpaid orders
		</Label>
		<ListBox
			Grid.Row="2"
			Grid.Column="0"
			x:Name="uc_UnpaidOrderList"
			DisplayMemberPath="Num"
			ItemsSource="{Binding UnpaidOrders, ElementName=uc_this}"
			MouseDoubleClick="unpaidOrdersList_OnMouseDoubleClick" />

		<Label
			Grid.Row="1"
			Grid.Column="1"
			FontWeight="Bold">
			Paid orders
		</Label>
		<ListBox
			Grid.Row="2"
			Grid.Column="1"
			DisplayMemberPath="Num"
			ItemsSource="{Binding PaidOrders, ElementName=uc_this}" />
	</Grid>
</Window>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using ObservableComputations;

namespace ObservableComputationsExample
{
	public partial class MainWindow : Window
	{
		public ObservableCollection<Order> Orders { get; }
		public ObservableCollection<Order> PaidOrders { get; }
		public ObservableCollection<Order> UnpaidOrders { get; }
		private readonly OcConsumer _consumer = new OcConsumer();

		// OcDispatcher for computations in the background thread
		OcDispatcher _ocDispatcher = new OcDispatcher();
		
		WpfOcDispatcher _wpfOcDispatcher;

		public MainWindow()
		{
			_wpfOcDispatcher = new WpfOcDispatcher(this.Dispatcher);
			
			Orders = new ObservableCollection<Order>();
			
			fillOrdersFromDb();			

			PaidOrders = 
				Orders.CollectionDispatching(_ocDispatcher) // direct the computation to the background thread
				.Filtering(o => o.PaidPropertyDispatching.Value)
				.CollectionDispatching(_wpfOcDispatcher, (int)DispatcherPriority.Background) // return the computation to the main thread
				.For(_consumer);

			UnpaidOrders = 
				Orders
				.Filtering(o => !o.Paid)
				.For(_consumer);

			InitializeComponent();
		}

		private void fillOrdersFromDb()
		{
			Thread thread = new Thread(() =>
			{
				Thread.Sleep(1000); // accessing DB
				Random random = new Random();
				for (int i = 0; i < 5000; i++)
				{
					Order order = new Order(i, _ocDispatcher, _wpfOcDispatcher);
					order.Paid = Convert.ToBoolean(random.Next(0, 3));
					this.Dispatcher.Invoke(() => Orders.Add(order), DispatcherPriority.Background);
				}

				this.Dispatcher.Invoke(
					() => uc_LoadingIndicator.Visibility = Visibility.Hidden, 
					DispatcherPriority.Background);
			});

			thread.Start();
		}

		private void unpaidOrdersList_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
		{
			((Order) uc_UnpaidOrderList.SelectedItem).Paid = true;
		}

		private void mainWindow_OnClosed(object sender, EventArgs e)
		{
			_ocDispatcher.Dispose();
			_consumer.Dispose();
		}
	}

	public class Order : INotifyPropertyChanged
	{
		public Order(int num, IOcDispatcher backgroundOcDispatcher, IOcDispatcher wpfOcDispatcher)
		{
			Num = num;
			PaidPropertyDispatching = new PropertyDispatching<Order, bool>(this, nameof(Paid), backgroundOcDispatcher, wpfOcDispatcher, 0, (int)DispatcherPriority.Background);

		}

		public int Num { get; }

		public PropertyDispatching<Order, bool> PaidPropertyDispatching { get; }

		private bool _paid;
		public bool Paid
		{
			get => _paid;
			set
			{
				_paid = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paid)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class WpfOcDispatcher : IOcDispatcher
	{
		private Dispatcher _dispatcher;

		public WpfOcDispatcher(Dispatcher dispatcher)
		{
			_dispatcher = dispatcher;
		}

		#region Implementation of IOcDispatcher

		public void Invoke(Action action, int priority, object parameter, object context)
		{
			_dispatcher.BeginInvoke(action, (DispatcherPriority)priority);
		}

		#endregion
	}
}

In this example, when we double-click on an unpaid order, we make it paid. In this case, since the Paid property changes in the main thread, we cannot read it in the background thread of _ocOcDispatcher. In order to read this property in the background thread of _ocOcDispatcher, it is necessary to dispatch changes of that property into that thread. This is done using the PropertyDispatching<THolder, TResult> class. Similar to the CollectionDispatching method, the constructor of the PropertyDispatching<THolder, TResult> class has the required parameter destinationOcDispatcher and the optional parameter sourceOcDispatcher. The difference is that

  • instead of enumerating the source collection and subscribing to the CollectionChanged event, the property value is read and the PropertyChanged event is subscribed.
  • the value passed to the sourceOcDispatcher parameter is used to dispatch the property value change (setter of PropertyDispatching<THolder, TResult>.Value) to the sourceOcDispatcher thread.

Note how DispatcherPriority.Background is passed through sourceOcDispatcherPriority parameter of PropertyDispatching class constructor to WpfOcDispatcher.Invoke method.

The above example is not the only design option. Here is another option (XAML has not changed):

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using ObservableComputations;

namespace ObservableComputationsExample
{
	public partial class MainWindow : Window
	{
		public ObservableCollection<Order> Orders { get; }
		public ObservableCollection<Order> PaidOrders { get; }
		public ObservableCollection<Order> UnpaidOrders { get; }
		private readonly OcConsumer _consumer = new OcConsumer();

		// OcDispatcher for computations in the background thread
		ObservableComputations.OcDispatcher _ocDispatcher = new ObservableComputations.OcDispatcher();
		
		WpfOcDispatcher _wpfOcDispatcher;

		public MainWindow()
		{
			_wpfOcDispatcher = new WpfOcDispatcher(this.Dispatcher);
			
			Orders = new ObservableCollection<Order>();
			
			fillOrdersFromDb();			

			PaidOrders = 
				Orders
				.Filtering(o => o.PaidPropertyDispatching.Value)
				.CollectionDispatching(_wpfOcDispatcher, _ocDispatcher, (int)DispatcherPriority.Background) // direct the computation to the main thread from the background one
				.For(_consumer);

			UnpaidOrders = 
				Orders
				.Filtering(o => !o.PaidPropertyDispatching.Value)
				.CollectionDispatching(_wpfOcDispatcher, _ocDispatcher, (int)DispatcherPriority.Background) // direct the computation to the main thread from the background one
				.For(_consumer);

			InitializeComponent();
		}

		private void fillOrdersFromDb()
		{
			Thread thread = new Thread(() =>
			{
				Thread.Sleep(1000); // accessing DB
				Random random = new Random();
				for (int i = 0; i < 5000; i++)
				{
					Order order = new Order(i, _ocDispatcher, _wpfOcDispatcher);
					order.Paid = Convert.ToBoolean(random.Next(0, 3));
					_ocDispatcher.Invoke(() => Orders.Add(order));
				}

				this.Dispatcher.Invoke(
					() => uc_LoadingIndicator.Visibility = Visibility.Hidden, 
					DispatcherPriority.Background);
			});

			thread.Start();
		}

		private void unpaidOrdersList_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
		{
			((Order) uc_UnpaidOrderList.SelectedItem).Paid = true;
		}

		private void mainWindow_OnClosed(object sender, EventArgs e)
		{
			_ocDispatcher.Dispose();
			_consumer.Dispose();
		}
	}

	public class Order : INotifyPropertyChanged
	{
		public Order(int num, IOcDispatcher backgroundOcDispatcher, IOcDispatcher wpfOcDispatcher)
		{
			Num = num;
			PaidPropertyDispatching = new PropertyDispatching<Order, bool>(this, nameof(Paid), backgroundOcDispatcher, wpfOcDispatcher, 0, (int)DispatcherPriority.Background);

		}

		public int Num { get; }

		public PropertyDispatching<Order, bool> PaidPropertyDispatching { get; }

		private bool _paid;
		public bool Paid
		{
			get => _paid;
			set
			{
				_paid = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paid)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class WpfOcDispatcher : IOcDispatcher
	{
		private Dispatcher _dispatcher;

		public WpfOcDispatcher(Dispatcher dispatcher)
		{
			_dispatcher = dispatcher;
		}

		#region Implementation of IOcDispatcher

		public void Invoke(Action action, int priority, object parameter, object context)
		{
			_dispatcher.Invoke(action, (DispatcherPriority)priority);
		}

		#endregion
	}
}

And one more (XAML has not changed):

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using ObservableComputations;

namespace ObservableComputationsExample
{
	public partial class MainWindow : Window
	{
		public ObservableCollection<Order> Orders { get; }
		public ObservableCollection<Order> PaidOrders { get; }
		public ObservableCollection<Order> UnpaidOrders { get; }
		private readonly OcConsumer _consumer = new OcConsumer();

		// OcDispatcher for computations in the background thread
		OcDispatcher _ocDispatcher = new OcDispatcher();
		
		WpfOcDispatcher _wpfOcDispatcher;

		public MainWindow()
		{
			_wpfOcDispatcher = new WpfOcDispatcher(this.Dispatcher);
			
			Orders = new ObservableCollection<Order>();

			PaidOrders = 
				Orders
				.Filtering(o => o.PaidPropertyDispatching.Value)
				.CollectionDispatching(_wpfOcDispatcher, (int)DispatcherPriority.Background) // direct the computation to the main thread
				.For(_consumer);

			UnpaidOrders = 
				Orders
				.Filtering(o => !o.PaidPropertyDispatching.Value)
				.CollectionDispatching(_wpfOcDispatcher, (int)DispatcherPriority.Background) // direct the computation to the main thread
				.For(_consumer);

			InitializeComponent();

			fillOrdersFromDb();
		}

		private void fillOrdersFromDb()
		{
			Thread thread = new Thread(() =>
			{
				Thread.Sleep(1000); // accessing DB
				Random random = new Random();
				for (int i = 0; i < 5000; i++)
				{
					Order order = new Order(i, _ocDispatcher, _wpfOcDispatcher);
					order.Paid = Convert.ToBoolean(random.Next(0, 3));
					_ocDispatcher.Invoke(() => Orders.Add(order));
				}

				this.Dispatcher.Invoke(
					() => uc_LoadingIndicator.Visibility = Visibility.Hidden, 
					DispatcherPriority.Background);
			});

			thread.Start();
		}

		private void unpaidOrdersList_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
		{
			((Order) uc_UnpaidOrderList.SelectedItem).Paid = true;
		}

		private void mainWindow_OnClosed(object sender, EventArgs e)
		{
			_ocDispatcher.Dispose();
			_consumer.Dispose();
		}
	}

	public class Order : INotifyPropertyChanged
	{
		public Order(int num, IOcDispatcher backgroundOcDispatcher, IOcDispatcher wpfOcDispatcher)
		{
			Num = num;
			PaidPropertyDispatching = new PropertyDispatching<Order, bool>(this, nameof(Paid), backgroundOcDispatcher, wpfOcDispatcher, 0, (int)DispatcherPriority.Background);

		}

		public int Num { get; }

		public PropertyDispatching<Order, bool> PaidPropertyDispatching { get; }

		private bool _paid;
		public bool Paid
		{
			get => _paid;
			set
			{
				_paid = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paid)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class WpfOcDispatcher : IOcDispatcher
	{
		private Dispatcher _dispatcher;

		public WpfOcDispatcher(Dispatcher dispatcher)
		{
			_dispatcher = dispatcher;
		}

		#region Implementation of IOcDispatcher

		public void Invoke(Action action, int priority, object parameter, object context)
		{
			_dispatcher.Invoke(action, (DispatcherPriority)priority);
		}

		#endregion
	}
}

Dispatching IReadScalar<TValue>

IReadScalar<TValue> was first mentioned here. In addition to the CollectionDispatching method, ObservableComputations contains the ScalarDispatching method. Its use is completely analogous to the use of PropertyDispatching, but with ScalarDispatching you can dispatch more than just properties. Using ScalarDispatching you can implement property dispatching, but using the PropertyDispatching<THolder, TResult> class it is simpler and faster.

Parallel computations in background threads

In the previous examples, we saw how the computation is performed in one background thread. Using the dispatch methods described above, it is possible to organize computations in several background threads, the results of which are concurrently combined in another thread (main or background).

Using OcDispatcher class

OcDispatcher class has methods that you can call if necessary

  • Invoke* - for synchronous and asynchronous execution of a delegate in the thread of an instance of OcDispatcher class, for example, for changing the source data for computations performed in the thread of an instance of OcDispatcher class. After calling Dispose method, these methods return without executing the passed delegate and without throwing an exception. Methods have setSynchronizationContext parameter. If you set this parameter to true, then the synchronization context corresponding to this call will be set for the duration of the passed delegate execution. This can be useful when using the await keyword inside the passed delegate.
  • InvokeAsyncAwaitable - these methods return an instance of System.Threading.Tasks.Task class and can be used with await keyword.
  • ExecuteOtherInvocations - if the delegate passed to the Invoke* methods take a long time you may need to call ExecuteOtherInvocations. When ExecuteOtherInvocations is called other delegates are executed. It is possible to set the maximum number of delegates that should be executed or the approximate maximum time for their execution.

Variants of implementation of IOcDispatcher interface

So far, we have used a very simple implementation of the IOcDispatcher interface. For example, this:

public class WpfOcDispatcher : IOcDispatcher
{
	private Dispatcher _dispatcher;

	public WpfOcDispatcher(Dispatcher dispatcher)
	{
		_dispatcher = dispatcher;
	}

	#region Implementation of IOcDispatcher

	public void Invoke(Action action, int priority, object parameter, object context)
	{
		_dispatcher.Invoke(action, DispatcherPriority.Background);
	}

	#endregion
}

In this implementation, the System.Windows.Threading.Dispatcher.Invoke method is called. In other implementations, we called System.Windows.Threading.Dispatcher.BeginInvoke. The implementation options are not limited to this.

Buffering changes

When there are many changes to a collection in a short period of time and you do not want to make the separate invocation of destination dispatcher for each change and want to batch changes you may use such implementation of IOcDispatcher:

using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ObservableComputations;

public class WpfOcDispatcher : IOcDispatcher, IDisposable
{
	Subject<Action> _actions;

	private System.Windows.Dispatcher _dispatcher;

	public WpfOcDispatcher(System.Windows.Dispatcher dispatcher)
	{
		_dispatcher = dispatcher;

		_actions = new Subject<Action>();
		_actions.Buffer(TimeSpan.FromMilliseconds(300)).Subscribe(actions =>
		{
			_dispatcher.Invoke(() =>
			{
				for (var index = 0; index < actions.Count; index++)
				{
					actions[index]();
				}
			}, DispatcherPriority.Background);
		});
	}

	#region Implementation of IOcDispatcher

	public void Invoke(Action action, int priority, object parameter, object context)
	{
		_actions.OnNext(action);
	}

	#endregion

	#region Implementation of IDisposable

	public void Dispose()
	{
		_actions.Dispose();
	}

	#endregion
}

Another option is to suspend the dispatcher during many changes to the collection:

using System;
using System.Collections.Generic;
using System.Windows.Threading;
using ObservableComputations;

namespace Trader.Domain.Infrastucture
{
	public class WpfOcDispatcher : IOcDispatcher
	{
		private Dispatcher _dispatcher;

		public List<Action> _deferredActions = new List<Action>();

		private bool _isPaused;

		public bool IsPaused
		{
			get => _isPaused;
			set
			{
				if (_isPaused && !value)
				{
					_dispatcher.Invoke(() =>
					{
						foreach (Action deferredAction in _deferredActions)
						{
							deferredAction();
						}
					}, DispatcherPriority.Send);

					_deferredActions.Clear();
				}

				_isPaused = value;
			}
		}

		public WpfOcDispatcher(Dispatcher dispatcher)
		{
			_dispatcher = dispatcher;
		}

		#region Implementation of IDispatcher

		public void Invoke(Action action, int priority, object parameter, object context)
		{
			if (_isPaused)
			{
				_deferredActions.Add(action);
				return;
			}

			if (_dispatcher.CheckAccess())
				action();
			else
				_dispatcher.Invoke(action, DispatcherPriority.Send);
		}

		#endregion
	}
}

Usage example of this implementation see here.

Suppressing overly frequent changes

When dispatching properties (PropertyDispatching) and IReadScalar<TValue> (ScalarDispatching), ThrottlingOcDispatcher can be useful for suppressing overly frequent changes (for example a user input):

using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ObservableComputations;

public class ThrottlingOcDispatcher : IOcDispatcher, IDisposable
{
	Subject<Action> _actions;

	private System.Windows.Dispatcher _dispatcher;

	public WpfOcDispatcher(System.Windows.Dispatcher dispatcher)
	{
		_dispatcher = dispatcher;

		_actions = new Subject<Action>();
		_actions.Throttle(TimeSpan.FromMilliseconds(300)).Subscribe(action =>
		{
			_dispatcher.Invoke(action, DispatcherPriority.Background);
		});
	}

	#region Implementation of IOcDispatcher

	public void Invoke(Action action, int priority, object parameter, object context)
	{
		_actions.OnNext(action);
	}

	#endregion

	#region Implementation of IDisposable

	public void Dispose()
	{
		_actions.Dispose();
	}

	#endregion
}

Usage example of this implementation see here and here.

Prioritization in the OcDispatcher class

OcDispatcher class can perform prioritized processing of delegates passed to it, just like WPFs Dispatcher. By default, OcDispatcher has only 1 priority, but the constructor of that class has the parameter of number of possible priorities: prioritiesNumber. In the previous examples, you saw how to set priority to custom implementation of IOcDispatcher interface (WpfOcDispatcher) in dispatch methods calls (CollectionDispatching, ScalarDispatching, PropertyDispatching). You can set priority for instance of OcDispatcher class in the same way: through destinationOcDispatcherPriority or sourceOcDispatcherPriority parameters of dispatch methods. The default priority is lowest: 0;

Number of possible priorities of OcDispatcher should be minimal to minimize overheads.

Launch in a console application

The previous examples were WPF application examples. Similar examples can be run in a console application. This may be needed for unit tests.

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	class Program
	{
		static ObservableComputations.OcDispatcher _backgroundOcDispatcher = new ObservableComputations.OcDispatcher();
		
		static ObservableComputations.OcDispatcher _mainOcDispatcher = new ObservableComputations.OcDispatcher();
		static ObservableCollection<Order> Orders;

		static void Main(string[] args)
		{
			OcConsumer consumer = new OcConsumer();

			_mainOcDispatcher.Invoke(() =>
			{
				ObservableCollection<Order> paidOrders;
				ObservableCollection<Order> unpaidOrders;

				Orders = new ObservableCollection<Order>();

				paidOrders =
					Orders.CollectionDispatching(_backgroundOcDispatcher)  // direct the computation to the background thread
					.Filtering(o => o.PaidPropertyDispatching.Value)
					.CollectionDispatching(_mainOcDispatcher,
						_backgroundOcDispatcher) // return the computation to the main thread from the background one
					.For(consumer);

				unpaidOrders = Orders.Filtering(o => !o.Paid).For(consumer);

				paidOrders.CollectionChanged += (sender, eventArgs) =>
				{
					if (eventArgs.Action != NotifyCollectionChangedAction.Add) return;
					Console.WriteLine($"Paid order: {((Order) eventArgs.NewItems[0]).Num}" );
				};

				unpaidOrders.CollectionChanged += (sender, eventArgs) =>
				{
					if (eventArgs.Action != NotifyCollectionChangedAction.Add) return;
					Console.WriteLine($"Unpaid order: {((Order) eventArgs.NewItems[0]).Num}");
				};

				fillOrdersFromDb();
			});

			Console.ReadLine();

			consumer.Dispose();
			_mainOcDispatcher.Dispose();
			_backgroundOcDispatcher.Dispose();
		}

		private static void fillOrdersFromDb()
		{
			Thread thread = new Thread(() =>
			{
				Thread.Sleep(1000); // accessing DB
				Random random = new Random();
				for (int i = 0; i < 5000; i++)
				{
					Order order = new Order(i, _backgroundOcDispatcher, _mainOcDispatcher);
					order.Paid = Convert.ToBoolean(random.Next(0, 3));
					_mainOcDispatcher.Invoke(() => Orders.Add(order));
				}
			});

			thread.Start();
		}
	}

	public class Order : INotifyPropertyChanged
	{
		public Order(int num, IOcDispatcher backgroundOcDispatcher, IOcDispatcher mainOcDispatcher)
		{
			Num = num;
			PaidPropertyDispatching = new PropertyDispatching<Order, bool>(() => Paid, backgroundOcDispatcher, mainOcDispatcher);

		}

		public int Num { get; }

		public PropertyDispatching<Order, bool> PaidPropertyDispatching { get; }

		private bool _paid;
		public bool Paid
		{
			get => _paid;
			set
			{
				_paid = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paid)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}
}

Debugging user code

Is described here.

Tracking changes in a method return value

Before now we saw how ObservableComputations tracks changes in property values and collections via PropertyChanged and CollectionChanged events. ObservableComputations introduces a new interface and event for tracking changes in a method return value: INotifyMethodChanged interface and MethodChanged event. Here is an example:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class RoomReservation
	{
		public string RoomId { get; set; }
		public DateTime From { get; set; }
		public DateTime To { get; set; }
	}

	public class RoomReservationManager : INotifyMethodChanged
	{
		private List<RoomReservation> _roomReservations = new List<RoomReservation>();

		public void AddReservation(RoomReservation roomReservation)
		{
			_roomReservations.Add(roomReservation);
			MethodChanged?.Invoke(this, new MethodChangedEventArgs(
				nameof(IsRoomReserved),
				args =>
				{
					string roomId = (string) args[0];
					DateTime dateTime = (DateTime) args[1];
					return
						roomId == roomReservation.RoomId
						&& roomReservation.From < dateTime && dateTime < roomReservation.To;
				}));
		}

		public bool IsRoomReserved(string roomId, DateTime dateTime)
		{
			return _roomReservations.Any(rr => 
				rr.RoomId == roomId 
				&& rr.From < dateTime && dateTime < rr.To);
		}

		public event EventHandler<MethodChangedEventArgs> MethodChanged;
	}

	public class Meeting : INotifyPropertyChanged
	{
		private string _roomNeeded;
		public string RoomNeeded
		{
			get => _roomNeeded;
			set
			{
				_roomNeeded = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RoomNeeded)));
			}
		}

		private DateTime _dateTimeNeeded;
		public DateTime DateTimeNeeded
		{
			get => _dateTimeNeeded;
			set
			{
				_dateTimeNeeded = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DateTimeNeeded)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			OcConsumer consumer = new OcConsumer();

			RoomReservationManager roomReservationManager = new RoomReservationManager();
			Meeting planingMeeting = new Meeting()
			{
				RoomNeeded = "ConferenceHall", 
				DateTimeNeeded = new DateTime(2020, 02, 07, 15, 45, 00)
			};

			Computing<bool> isRoomReservedComputing = 
				new Computing<bool>(() =>
					roomReservationManager.IsRoomReserved(
						planingMeeting.RoomNeeded, 
						planingMeeting.DateTimeNeeded))
				.For(consumer);

			isRoomReservedComputing.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(Computing<bool>.Value))
				{
					// see changes here
				}
			};

			roomReservationManager.AddReservation(new RoomReservation()
			{
				RoomId = "ConferenceHall",
				From =  new DateTime(2020, 02, 07, 15, 00, 00),
				To =  new DateTime(2020, 02, 07, 16, 00, 00)
			});

			planingMeeting.DateTimeNeeded = new DateTime(2020, 02, 07, 16, 30, 00);
				
			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

As you see MethodChangedEventArgs contains ArgumentsPredicate property. The following value is passed to that property:

args =>
{
   string roomId = (string) args[0];
   DateTime dateTime = (DateTime) args[1];
   return
	roomId == roomReservation.RoomId
	&& roomReservation.From < dateTime && dateTime < roomReservation.To;
}

That property defines what values should have arguments in a method call so that the return value of that call changes.

ATTENTION: Code example given in this section is not a design standard, it is rather an antipattern: it contains code duplication and changes of properties of RoomReservation class are not tracked. That code is given only for demonstration of tracking changes in a method return value. See fixed code here.

Computations implementing INotifyMethodChanged

INotifyMethodChanged is implemented by the following computations:

  • Dictionaring (methods: ContainsKey, Indexer ([]), GetValueOrDefault).
  • ConcurrentDictionaring (methods: ContainsKey, Indexer ([]), GetValueOrDefault).
  • HashSetting (method: Contains).

Performance tips

Avoid nested parameter-dependent computations on big data

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		public event PropertyChangedEventHandler PropertyChanged;

		public int Num {get; set;}

		private string _type;
		public string Type
		{
			get => _type;
			set
			{
				_type = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Type)));
			}
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			ObservableCollection<Order> orders = 
				new ObservableCollection<Order>(new []
				{
					new Order{Num = 1, Type = "VIP"},
					new Order{Num = 2, Type = "Regular"},
					new Order{Num = 3, Type = "VIP"},
					new Order{Num = 4, Type = "VIP"},
					new Order{Num = 5, Type = "NotSpecified"},
					new Order{Num = 6, Type = "Regular"},
					new Order{Num = 7, Type = "Regular"}
				});

			ObservableCollection<string> selectedOrderTypes = new ObservableCollection<string>(new []
				{
					"VIP", "NotSpecified"
				});

			OcConsumer consumer = new OcConsumer();

			ObservableCollection<Order> filteredByTypeOrders = 
				orders.Filtering(o => 
					selectedOrderTypes.ContainsComputing(
						() => o.Type).Value)
				.For(consumer);		

			filteredByTypeOrders.CollectionChanged += (sender, eventArgs) =>
			{
				// see the changes (add, remove, replace, move, reset) here			
			};

			// Start the changing...
			orders.Add(new Order{Num = 8, Type = "VIP"});
			orders.Add(new Order{Num = 9, Type = "NotSpecified"});
			orders[4].Type = "Regular";
			orders.Move(4, 1);
			orders[0] = new Order{Num = 10, Type = "Regular"};
			selectedOrderTypes.Remove("NotSpecified");

			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

In the code above, selectedOrderTypes.ContainsComputing(() => o.Type) is a nested computation which is dependent on outer parameter o. These two circumstances lead to the fact that an instance of ContainsComputing class will be created for each order in the orders collection. This may impact performance and memory consumption if you have many orders. Fortunately, filteredByTypeOrders computation can be made "flat":

ObservableCollection<Order> filteredByTypeOrders =  orders
	.Joining(selectedOrderTypes, (o, ot) => o.Type == ot)
	.Selecting(oot => oot.OuterItem)
	.For(consumer);

This computation has a performance and memory consumption advantage.

Cache property (method) values

Suppose we have a long-computed property and we want to increase the performance of getting its value:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class ValueHolder : INotifyPropertyChanged
	{
		private string _value;

		public string Value
		{
			get
			{
				Thread.Sleep(100);
				return _value;
			}
			set
			{
				_value = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
			}
		}

		private Computing<string> _valueComputing;
		public Computing<string> ValueComputing => _valueComputing = 
			_valueComputing ?? new Computing<string>(() => Value);

		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			ValueHolder valueHolder = new ValueHolder();

			Stopwatch stopwatch = new Stopwatch();
			stopwatch.Start();
			for (int i = 0; i < 20; i++)
			{
				string value = valueHolder.Value;
			}
			stopwatch.Stop();
			Console.WriteLine($"Direct access to property: {stopwatch.ElapsedMilliseconds}");

			stopwatch.Restart();
			for (int i = 0; i < 20; i++)
			{
				string value = valueHolder.ValueComputing.Value;
			}
			stopwatch.Stop();
			Console.WriteLine($"Access to property via computing: {stopwatch.ElapsedMilliseconds}");
				
			Console.ReadLine();
		}
	}
}

Code above has the following output:

Direct access to property: 2155
Access to property via computing: 626

Differing<TResult> extension method

That extension method allows you to suppress extra raisings of PropertyChanged event (when the value of a property is not changed).

using System;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Angle : INotifyPropertyChanged
	{
		private double _rads;
		public double Rads
		{
			get
			{
				return _rads;
			}
			set
			{
				_rads = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Rads)));
			}
		}

		public static double DegreesToRads(double degrees) => degrees * (Math.PI / 180);

		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			Angle angle = new Angle(){Rads = Angle.DegreesToRads(0)};

			OcConsumer consumer = new OcConsumer();

			Computing<double> sinComputing = 
				new Computing<double>(
					() => Math.Round(Math.Sin(angle.Rads), 3)) // 0
				.For(consumer);

			Console.WriteLine($"sinComputing: {sinComputing.Value}");

			sinComputing.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(Computing<double>.Value))
				{
					Console.WriteLine($"sinComputing: {sinComputing.Value}");
				}
			};

			Differing<double> differingSinComputing = 
				sinComputing.Differing().For(consumer);
			Console.WriteLine($"differingSinComputing: {sinComputing.Value}");
			differingSinComputing.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(Computing<double>.Value))
				{
					Console.WriteLine($"differingSinComputing: {differingSinComputing.Value}");
				}
			};

			angle.Rads = Angle.DegreesToRads(30); // 0,5
			angle.Rads = Angle.DegreesToRads(180) - angle.Rads; // 0,5	
			angle.Rads = Angle.DegreesToRads(360 + 180) - angle.Rads; // 0,5
			angle.Rads = Angle.DegreesToRads(360) - angle.Rads; // -0,5
			
			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

Code above has the following output:

sinComputing: 0
differingSinComputing: 0
sinComputing: 0,5
differingSinComputing: 0,5
sinComputing: 0,5
sinComputing: 0,5
sinComputing: -0,5
differingSinComputing: -0,5

Sometimes the handling of every PropertyChanged event is long-time and may freeze UI (rerendering, recomputing). Use Differing extension method to decrease that effect.

Use capacity argument

If, after instantiating the collection computation class (e.g. Filtering), it is expected that the collection will grow significantly, it makes sense to pass the capacity argument to the constructor to reserve memory for the collection.

Use computations in background threads

Remember about YGNI whenever you want to develop a design with computations in background threads. Computations in background threads in described here.

Pausing

If you need to make a large number of changes in source data and would not process each change in your computations you can temporally stop computation (pause).

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		public event PropertyChangedEventHandler PropertyChanged;

		public int Num {get; set;}

		private decimal _price;
		public decimal Price
		{
			get => _price;
			set
			{
				_price = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Price)));
			}
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			ObservableCollection<Order> orders = 
				new ObservableCollection<Order>(new []
				{
					new Order{Num = 1, Price = 15},
					new Order{Num = 2, Price = 15},
					new Order{Num = 3, Price = 25},
					new Order{Num = 4, Price = 27},
					new Order{Num = 5, Price = 30},
					new Order{Num = 6, Price = 75},
					new Order{Num = 7, Price = 80}
				});

			// We start using ObservableComputations here!
			OcConsumer consumer = new OcConsumer();

			CollectionPausing<Order> ordersPausing = orders.CollectionPausing();

			Filtering<Order> expensiveOrders = 
				ordersPausing
					.Filtering(o => o.Price > 25)
					.For(consumer); 
			
			Debug.Assert(expensiveOrders is ObservableCollection<Order>);
			
			checkFiltering(orders, expensiveOrders); // Prints "True"

			expensiveOrders.CollectionChanged += (sender, eventArgs) =>
			{
				// see the changes (add, remove, replace, move, reset) here			
				checkFiltering(orders, expensiveOrders); // Prints "True"
			};

			// Start the changing...
			orders.Add(new Order{Num = 8, Price = 30});
			orders.Add(new Order{Num = 9, Price = 10});

			// Start many changes...
			ordersPausing.IsPaused = true;
			
			for (int i = 10; i < 1000; i++)
				orders.Add(new Order{Num = i, Price = 30});

			checkFiltering(orders, expensiveOrders); // Prints "False"

			ordersPausing.IsPaused = false;

			checkFiltering(orders, expensiveOrders); // Prints "True"

			Console.ReadLine();

			consumer.Dispose();
		}

		static void checkFiltering(
			ObservableCollection<Order> orders, 
			Filtering<Order> expensiveOrders)
		{
			Console.WriteLine(expensiveOrders.SequenceEqual(
				orders.Where(o => o.Price > 25)));
		}
	}
}

Note that while calling "ordersPausing.IsPaused = false;" ordersPausing generates CollectionChanged event with NotifyCollectionChangedAction.Reset. This is the default behavior. You can set value of resumeType parameter of CollectionPausing extension method to CollectionPausingResumeType.ReplayChanges so instead of NotifyCollectionChangedAction.Reset ordersPausing will replay exactly the same sequence of changes that were made during the pause. ObservableComputations includes also ScalarPausing extension method. Its usage is analogous. Instead of CollectionPausingResumeType ScalarPausing allow setting how many last changes it will replay on the resume. The default value is 1. null corresponds to all the changes.

Design tips

Lazily initialized computation

If some computation is needed only for particular scenarios or you want delay initialization until the computation becomes needed, lazily initialized computation is advisable. Here is an example:

private Computing<string> _valueComputing;
public Computing<string> ValueComputing => _valueComputing = 
   _valueComputing ?? new Computing<string>(() => Value).For(_consumer);

Use public read-only structures instead of encapsulated private members

Code example given in "Tracking changes in a method return value" section is not a design standard, it is rather an antipattern: it contains code duplication and changes of properties of RoomReservation class are not tracked. That code is given only for demonstration of tracking changes in a method return value. Here is the fixed design:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class RoomReservation : INotifyPropertyChanged
	{
		private string _roomId;
		public string RoomId
		{
			get => _roomId;
			set
			{
				_roomId = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RoomId)));
			}
		}


		private DateTime _from;
		public DateTime From
		{
			get => _from;
			set
			{
				_from = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(From)));
			}
		}

		private DateTime _to;
		public DateTime To
		{
			get => _to;
			set
			{
				_to = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(To)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class RoomReservationManager 
	{
		private ObservableCollection<RoomReservation> _roomReservations = new ObservableCollection<RoomReservation>();
		private ReadOnlyObservableCollection<RoomReservation> _roomReservationsReadOnly;

		public RoomReservationManager()
		{
			_roomReservationsReadOnly = new ReadOnlyObservableCollection<RoomReservation>(_roomReservations);
		}

		public void AddReservation(RoomReservation roomReservation)
		{
			_roomReservations.Add(roomReservation);;
		}

		public ReadOnlyObservableCollection<RoomReservation> RoomReservations =>
			_roomReservationsReadOnly;
	}

	public class Meeting : INotifyPropertyChanged
	{
		private string _roomNeeded;
		public string RoomNeeded
		{
			get => _roomNeeded;
			set
			{
				_roomNeeded = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RoomNeeded)));
			}
		}

		private DateTime _dateTimeNeeded;
		public DateTime DateTimeNeeded
		{
			get => _dateTimeNeeded;
			set
			{
				_dateTimeNeeded = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DateTimeNeeded)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			RoomReservationManager roomReservationManager = new RoomReservationManager();
			Meeting planingMeeting = new Meeting()
			{
				RoomNeeded = "ConferenceHall", 
				DateTimeNeeded = new DateTime(2020, 02, 07, 15, 45, 00)
			};

			OcConsumer consumer = new OcConsumer();

			AnyComputing<RoomReservation> isRoomReservedComputing = 
				roomReservationManager.RoomReservations.AnyComputing<RoomReservation>(rr => 
					rr.RoomId == planingMeeting.RoomNeeded
					&& rr.From < planingMeeting.DateTimeNeeded 
					&& planingMeeting.DateTimeNeeded < rr.To)
				.For(consumer);

			isRoomReservedComputing.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(Computing<bool>.Value))
				{
					// see changes here
				}
			};

			roomReservationManager.AddReservation(new RoomReservation()
			{
				RoomId = "ConferenceHall",
				From =  new DateTime(2020, 02, 07, 15, 00, 00),
				To =  new DateTime(2020, 02, 07, 16, 00, 00)
			});

			planingMeeting.DateTimeNeeded = new DateTime(2020, 02, 07, 16, 30, 00);
				
			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

Note that type of RoomReservationManager._roomReservations is changed to ObservableCollection<RoomReservation> and RoomReservationManager.RoomReservations member of type System.Collections.ObjectModel.ReadOnlyObservableCollection<RoomReservation> has been added.

Shorten your code

See here and here.

Do not create extra variables

See here

Applications of Using<TResult> extension method

Clear expressions

See the end lines of Arbitrary expression observing.

Variable declaration in a computations chain

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class OrderLine : INotifyPropertyChanged
	{
		private decimal _price;
		public decimal Price
		{
			get
			{
				return _price;
			}
			set
			{
				_price = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Price)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class Order : INotifyPropertyChanged
	{
		public ObservableCollection<OrderLine> Lines = new ObservableCollection<OrderLine>();

		public OcConsumer Consumer;

		private decimal _discount;
		public decimal Discount
		{
			get
			{
				return _discount;
			}
			set
			{
				_discount = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Discount)));
			}
		}

		private Computing<decimal> _priceWithDiscount;
		public Computing<decimal> PriceWithDiscount
		{
			get
			{
				if (_priceWithDiscount == null)
				{
					// first step
					Summarizing<decimal> totalPrice 
						= Lines.Selecting(l => l.Price).Summarizing(); 
						
					// second step
					_priceWithDiscount = 
						new Computing<decimal>(
							() => totalPrice.Value - totalPrice.Value * Discount)
						.For(Consumer);
				}

				return _priceWithDiscount;
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			OcConsumer consumer = new OcConsumer();
			Order order = new Order(){Discount = 0.25m, Consumer = consumer};
			order.Lines.Add(new OrderLine(){Price = 100});
			order.Lines.Add(new OrderLine(){Price = 150});
			order.Lines.Add(new OrderLine(){Price = 50});

			Console.WriteLine(order.PriceWithDiscount.Value);

			order.Lines[1].Price = 130;

			Console.WriteLine(order.PriceWithDiscount.Value);
				
			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

Pay attention to PriceWithDiscount property. In the body of that property, we construct _priceWithDiscount computation in two steps. Can we refactor PriceWithDiscount property to an expression body? Yes:

public Computing<decimal> PriceWithDiscount => _priceWithDiscount = _priceWithDiscount ?? 
   Lines.Selecting(l => l.Price).Summarizing().Using(p => p.Value - p.Value * Discount).For(Consumer);

In the code above p parameter is the result of Lines.Selecting(l => l.Price).Summarizing(). So p parameter is a kind of variable. The following code is incorrect as changes in OrderLine.Price property and Order.Lines collection is not reflected in the result computation:

public Computing<decimal> PriceWithDiscount => _priceWithDiscount = _priceWithDiscount ?? 
   Lines.Selecting(l => l.Price).Summarizing().Value.Using(p => p - p * Discount).For(Consumer);

In this code p parameter has type decimal, not Summarizing<decimal>; as in correct variant. See here for details.

Tracking previous value of IReadScalar<TValue>

IReadScalar<TValue> is mentioned here for the first time. There are not built-in facilities to get the previous value of a property while handling PropertyChanged event. ObservableComputation helps you and provides PreviousTracking<TResult> and WeakPreviousTracking<TResult> extension methods.

using System;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		private string _deliveryDispatchCenter;
		public string DeliveryDispatchCenter
		{
			get
			{
				return _deliveryDispatchCenter;
			}
			set
			{
				_deliveryDispatchCenter = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DeliveryDispatchCenter)));
			}
		}


		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			Order order = new Order()
			{
				DeliveryDispatchCenter = "A"
			};

			OcConsumer consumer = new OcConsumer();

			PreviousTracking<string> previousTracking = 
				new Computing<string>(() => order.DeliveryDispatchCenter)
				.PreviousTracking()
				.For(consumer);

			previousTracking.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(Computing<double>.Value))
				{
					Console.WriteLine($"Current dispatch center: {previousTracking.Value}; Previous dispatch center: {previousTracking.PreviousValue};");
				}
			};

			order.DeliveryDispatchCenter = "B";
			order.DeliveryDispatchCenter = "C";
			
			Console.ReadLine();

			consumer.Dispose();
		}
	}
}

Code above has the following output:

Current dispatch center: B; Previous dispatch center: A;
Current dispatch center: C; Previous dispatch center: B;

Note that changes of PreviousValue property are trackable by PropertyChanged event so you can include that property in your observable computations.

Note that the instance of PreviousTracking<TResult> has a strong reference to previous TResult value (PreviousValue property) (in case TResult is a reference type). Account it when you think will about garbage collecting and memory leaks. WeakPreviousTracking<TResult> can help you. Instead of PreviousValue property WeakPreviousTracking<TResult> includes TryGetPreviousValue method. Changes of the return value of that method aren't trackable, so you cannot include it in your observable computations.

Accessing a property via reflection

The following code will not work correctly:

using System;
using System.ComponentModel;
using System.Reflection;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		private decimal _price;
		public decimal Price
		{
			get
			{
				return _price;
			}
			set
			{
				_price = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Price)));
			}
		}


		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			Order order = new Order()
			{
				Price = 1
			};

			PropertyInfo pricePropertyInfo = typeof(Order).GetProperty(nameof(Order.Price));
			
			OcConsumer consumer = new OcConsumer();

			Computing<decimal> priceReflectedComputing =
				new Computing<decimal>(() => (decimal)pricePropertyInfo.GetValue(order))
				.For(consumer);

			priceReflectedComputing.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(PropertyAccessing<decimal>.Value))
				{
					Console.WriteLine(priceReflectedComputing.Value);
				}
			};  

			order.Price = 2;
			order.Price = 3;
		
			Console.ReadLine();
			
			consumer.Dispose();
		}
	}
}

Code above has no output, as changes of the return value of GetValue method cannot be tracked. Here is the fixed code:

using System;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		private decimal _price;
		public decimal Price
		{
			get
			{
				return _price;
			}
			set
			{
				_price = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Price)));
			}
		}


		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			Order order = new Order()
			{
				Price = 1
			};
			
			OcConsumer consumer = new OcConsumer();

			PropertyAccessing<decimal> priceReflectedComputing =
				order.PropertyAccessing<decimal>(nameof(Order.Price))
				.For(consumer);

			priceReflectedComputing.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(PropertyAccessing<decimal>.Value))
				{
					Console.WriteLine(priceReflectedComputing.Value);
				}
			};  

			order.Price = 2;
			order.Price = 3;
			
			Console.ReadLine();
			
			consumer.Dispose();
		}
	}
}

In the code above we use PropertyAccessing extension method. Be sure you are aware of Passing arguments as non-observables and observables: in the code above, the first argument (order) of PropertyAccessing extension method is passed as non-observable. In the following code that argument is passed as observable.

using System;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		private decimal _price;
		public decimal Price
		{
			get
			{
				return _price;
			}
			set
			{
				_price = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Price)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	public class Manager : INotifyPropertyChanged
	{
		private Order _processingOrder;
		public Order ProcessingOrder
		{
			get
			{
				return _processingOrder;
			}
			set
			{
				_processingOrder = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ProcessingOrder)));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}

	class Program
	{
		static void Main(string[] args)
		{
			Order order = new Order()
			{
				Price = 1
			};

			Manager manager = new Manager(){ProcessingOrder = order};
			
			OcConsumer consumer = new OcConsumer();

			PropertyAccessing<decimal> priceReflectedComputing =
				new Computing<Order>(() => manager.ProcessingOrder)
					.PropertyAccessing<decimal>(nameof(Order.Price))
				.For(consumer);

			priceReflectedComputing.PropertyChanged += (sender, eventArgs) =>
			{
				if (eventArgs.PropertyName == nameof(PropertyAccessing<decimal>.Value))
				{
					Console.WriteLine(priceReflectedComputing.Value);
				}
			};  

			order.Price = 2;
			order.Price = 3;
			manager.ProcessingOrder = 
				new Order()			
				{
					Price = 4
				};
			
			Console.ReadLine();
			
			consumer.Dispose();
		}
	}
}

The following code will not work correctly as changes in manager.ProcessingOrder is not reflected in priceReflectedComputing as the first argument of PropertyAccessing extension method is passed as non-observable:

PropertyAccessing<decimal> priceReflectedComputing 
   = manager.ProcessingOrder.PropertyAccessing<decimal>(nameof(Order.Price)).For(consumer);

If object reference for which a property value is being accessed is null PropertyAccessing<TResult>.Value returns the default value of TResult. You can modify that value by passing the defaultValue parameter.

Binding

Binding class and extension method allows you to bind two arbitrary expressions. The first expression is a source. The second expression is a target. The complexity of the expressions is not limited. The first expression is passed as an expression tree. The second expression is squashed as a delegate. If the source expression value is changed, the new value is assigned to the target expression:

using System;
using System.ComponentModel;
using ObservableComputations;

namespace ObservableComputationsExamples
{
	public class Order : INotifyPropertyChanged
	{
		public event PropertyChangedEventHandler PropertyChanged;

		private string _deliveryAddress;
		public string DeliveryAddress
		{
			get => _deliveryAddress;
			set
			{
				_deliveryAddress = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DeliveryAddress)));
			}
		}
	}

	public class Car : INotifyPropertyChanged
	{
		public event PropertyChangedEventHandler PropertyChanged;

		private string _destinationAddress;
		public string DestinationAddress
		{
			get => _destinationAddress;
			set
			{
				_destinationAddress = value;
				PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DestinationAddress)));
			}
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			Order order = new Order(){DeliveryAddress = ""};
			Car assignedDeliveryCar = new Car(){DestinationAddress = ""};

			Binding<string> deliveryAddressBinding = new Binding<string>(
				() => order.DeliveryAddress,
				da => assignedDeliveryCar.DestinationAddress = da);

			Console.WriteLine(assignedDeliveryCar.DestinationAddress);

			order.DeliveryAddress = "A";
			Console.WriteLine(assignedDeliveryCar.DestinationAddress);

			order.DeliveryAddress = "B";
			Console.WriteLine(assignedDeliveryCar.DestinationAddress);

			Console.ReadLine();

			deliveryAddressBinding.Dispose();
		}
	}
}

In the code above, we bind order.DeliveryAddress and assignedDeliveryCar.DestinationAddress. order.DeliveryAddress is a binding source. assignedDeliveryCar.DestinationAddress is a binding target.

Binding extension method extends IReadScalar<TValue>, an instance of which is a binding source.

Can I use IList<T> with ObservableComputations?

If you have IList<T> collection of a class that does not implement INotifyCollectionChanged (for example List<T>), you can use it with ObservableComputations. See

https://github.com/gsonnenf/Gstc.Collections.ObservableLists

Nuget: https://www.nuget.org/packages/Gstc.Collections.ObservableLists