If you haven't been paying a lot of attention to the barrage of new Microsoft technology, LINQ is one of the key new features of the .NET Framework 3.5. LINQ, short for "Language Integrated Query," lets you express filtering, sorting, grouping, and aggregation tasks using your language of choice with a syntax that looks pretty familiar to anyone with experience writing SQL queries for relational databases.
No matter what kind of application you're writing or what industry the application is for, there are some basic tasks that almost every programmer needs to do. At some point, you will likely have a list of data you need sorted, filtered, grouped, or aggregated in some fashion. Before LINQ, this involved the tedium of creating a loop to iterate through all of the list items, then adding the items that match a filter to the output collection, making sure to preserve the proper sort order. Using LINQ, you can now write statements that look like this, dramatically simplifying the code:
var results = from customer in CustomerList where customer.Age > 21 orderby customer.LastName select customer;
Through the magic of the language extensions feature in .NET 3.5, this query will essentially turn into something that looks like this:
IEnumerable<Customer> results = CustomerList.Where( f => f.Age > 21).OrderBy( o=> o.LastName ).Select( s => s);
Don't worry if this syntax looks ugly to youthat's the entire purpose behind embedding language shortcuts for LINQ queries directly in C# and VB.NET. What you see in the preceding code sample are language extensions and lambda functions. The Where language extension to IEnumerable<T> lets you filter pretty much any list, so long as the items in the list implement IEquatable<T>.
On its own, LINQ is a fantastic addition to the arsenal of any developer writing virtually any kind of application. The true power of the .NET Framework 3.5 comes with your ability to enhance the functionality of LINQ using language extensions. Language extensions let you create your own overridden implementations for Where, OrderBy, GroupBy, and the like. Continuous LINQ (CLINQ) is just one sample of such an extension to LINQ that was created using this facility.
When building database applications, the general model is you write a query to obtain some data. Data can then be modified and then sent back to the database. In general, however, you do not expect to get continuous updates from the database as data in the underlying tables change. With most relational databases, the performance hit from doing such a thing would bring the database to a halt and probably impact the application as well.
Other applications need to be quickly updated in response to continuously changing data. An example from the financial industry would be an application that listens to market data ticks. Using the current implementation of LINQ, you could write a query like this:
var tickData = from tick in MarketData.Ticks where tick.Symbol == "AAPL" orderby tick.Ask descending select tick;
The problem with this implementation is that tickData is completely stale. Immediately after you run the query, tickData will never grow or shrink dynamically on its own. It would be nice if the contents of tickData could continuously update every time new market data is added to the original source of the query, MarketData.Ticks. This is what the Continuous LINQ extension does.
Continuous LINQ works by attaching listeners, or adapters, to the source collection when the Where, OrderBy, and GroupBy language extensions are invoked. At some point in the future, it might support other extensions as well, but these were the most important. The adapter's job is to listen to changes to the source collection, including all items in the source collection, and decide if that change needs to be propagated to the destination collection. For example, using the preceding market-data query, if MarketData.Ticks was derived from ContinuousCollection<T>, then adapters would be created that listen to the source data. If a new item is added to the source collection that has a symbol name of "AAPL," then the item is automatically added to the results. These adapters also maintain sort order and can be nested and chained at will. Figure 1 illustrates the flow of data through adapters from a typical CLINQ query.
You can extend any IEnumerable<T> derivative class and provide your own domain-specific implementations for the where, orderby, and groupby LINQ query operators. Listing One illustrates the where and orderby extensions to a class called ContinuousCollection<T>. This class is really nothing more than a thread-safe derivative of ObservableCollection<T> that ensures that all change publications take place on the main GUI dispatcher thread to let all WPF GUIs and CLINQ adapters see the changes.
public static class ContinuousQueryExtension { #region Where public static ContinuousCollection<T> Where<T>( this ContinuousCollection<T> source, Func<T, bool> filterFunc) where T: IEquatable<T> { Trace.WriteLine("Filtering Observable Collection."); ContinuousCollection<T> output = new ContinuousCollection<T>(); FilteringViewAdapter<T> fva = new FilteringViewAdapter<T>(source, output, filterFunc); return output; } #endregion #region OrderBy public static ContinuousCollection<TSource> OrderBy<TSource, TKey>( this ContinuousCollection<TSource> source, Func<TSource, TKey> keySelector) where TSource : IEquatable<TSource> where TKey : IComparable { Trace.WriteLine("Ordering Observable Collection (Ascending)."); ContinuousCollection<TSource> output = new ContinuousCollection<TSource>(); SortingViewAdapter<TSource, TKey> sva = new SortingViewAdapter<TSource, TKey>( source, output, new FuncComparer<TSource, TKey>(keySelector, false)); return output; } #endregion #region OrderByDescending public static ContinuousCollection<TSource> OrderByDescending<TSource, TKey>( this ContinuousCollection<TSource> source, Func<TSource, TKey> keySelector) where TSource : IEquatable<TSource> where TKey : IComparable { Trace.WriteLine("Ordering Observable Collection (Descending)."); ContinuousCollection<TSource> output = new ContinuousCollection<TSource>(); SortingViewAdapter<TSource, TKey> sva = new SortingViewAdapter<TSource, TKey>( source, output, new FuncComparer<TSource, TKey>(keySelector, true)); return output; } #endregion }
In the constructor of the adapters, the adapter attaches event handlers for the collection-changed events and the property-changed events for each item in the collection. Then, in response to these changes, the adapter determines if the changed item needs to be propagated to the destination collection or if it needs to be removed from the destination collection (for example, a modified item no longer satisfies the query predicate and needs to be removed). The removal will cascade down the chain so it makes no difference how many where, groupby, or orderby adapters the change needs to pass through.
The great thing about CLINQ and other language extensions enabled through LINQ is that all of the complexity is hidden from developers. You don't need to look at Listing One to use the advanced functionalitysimply write a LINQ query against a ContinuousCollection instead of a standard list and it "just works."
There are lots of different samples that can be written to illustrate CLINQ in action, but one of the more applicable samples I've found is an Order Book. An order book application monitors the market data for a select subset of symbols. Without CLINQ, I would have to set up manual monitoring threads that listen for changes to the underlying market data and then manually propagate those changes to the model, which is then bound to the GUI. This creates a lot of moving parts, makes debugging difficult, and can be very error-prone. With CLINQ, I can simply set up some queries in the controller code and everything is dynamically updated as needed. For example, in the demo order book application, to open a new book and create a variable that contains just the subset of market data belonging to a particular symbol, you can use a simple CLINQ query (a LINQ query against a ContinuousCollection). Listing Two shows the controller method SubscribeToSymbol, which creates a new window and sets the AskTicks and BidTicks properties of that window to CLINQ queries.
public void SubscribeToSymbol(MarketSymbol symbol) { if (!ModelRoot.Current.SubscribedSymbols.Contains(symbol)) { ModelRoot.Current.SubscribedSymbols.Add(symbol); MarketDataBook newBook = new MarketDataBook(); // WPF Window newBook.Symbol = symbol; newBook.AskTicks = from tick in ModelRoot.Current.MarketData where tick.Side == TickSide.Ask && tick.Symbol == symbol orderby tick.Price descending select tick; // SELL newBook.BidTicks = from tick in ModelRoot.Current.MarketData where tick.Side == TickSide.Bid && tick.Symbol == symbol orderby tick.Price select tick; // BUY _monitorBooks.Add(symbol, newBook); } }
Once the AskTicks and BidTicks properties have been set, those collections will be automatically updated as more market data streams in, without any further effort on the part of the programmer. This is a powerful way of dealing with message processing and streaming data.