Using variable KeySelectors in LINQ Queries

Posted by PR on

One of my favorite C# features for operating on data sets is Language Integrated Query (LINQ). LINQ makes it possible to query data using an intuitive API that adheres to the C# language constructs - which is huge for the sake of code consistency and correctness.


Figure:Switching the interface from vertical-orientation to horizontal-orientation via menu shortcut.

For example, if working with a data set containing OpenCV detection statistics, a LINQ query could be written to select detections within a certain range of accuracy like this:

var accurateDetections = detections.Where(c => c.accuracy > 0.95);

The part of that query I want to highlight is the "c.accuracy > 0.95" condition, which is what is called the KeySelector - it is responsible for returning collection items based on each item's processing by that condition. Instead of hard-coding the KeySelector, variable KeySelectors can be used instead, which enables LINQ statements and LINQ-dependent functions to be parameterized.

Recently, while I was working on a data analysis WinUI application, I noticed that the API example for grouping DataGrid items uses LINQ. The example provided a method for creating one type of grouping based on a hard-coded KeySelector:

var query = from item in list orderby item group item by item.Range into g select new { ... };

This query works well for a single grouping (by "Range"), but what if I want to utilize that same statement for multiple types of groupings, like "Distance" or "Timestamp" or "Id"? By converting item.Range to keySelector(item), and providing Func keySelector as a functional parameter, any condition can be used with the same query! In my app, which visualizes eBird data, I use multiple keySelectors in this way to define the conditions for groupings by genus or by common name:

public Func KeySelectorGenus = new (item => item.ScientificName.Split(' ').First());

public Func KeySelectorCommonName = new(item => item.CommonName.Trim().Split(' ').Last());

The end result is that I use the same LINQ-based grouping infrastructure for any type of grouping that can be expressed as a keySelector.

Copyright © ptr-cs