Unearthing LINQ’s hidden gems - part 1
I was writing a little application using Visual Studio 2008 today and came across a few nifty but lesser known features of LINQ and though to share those with you:
How to filter queries based on the position of elements in data source
System.Linq.Enumerable class provides extension methods that enable query expressions in LINQ. For example, it has extension methods for the “where”, “from” and “join” clauses. This Enumerable class has 2 extension methods for the “where” clause:
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, bool> predicate);
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, int, bool> predicate);
The first method is used for when you decide to filter the source sequence based on the result of a predicate. This predicate takes an item from the source and returns a Boolean indicating whether the item should be included in the result.
The second extension method on the other hand takes an extra argument of type integer. This integer provides the zero-based index of the item in the source. Knowing the index, you can provide logic that filters based on the position of elements in the source.
Here is a simple example:
var results
= orders
.Where((o, index) => (index % 2 == 1 && o.Total > 2))
.Select(o => o);
In fact this position parameter is also available to you in some other extension methods such as “Select”.
The SelectMany extension method
In scenarios that you would like to return a single merged list of child elements, use SelectMany instead of Select. For instance, if you are interested in returning a single list of products ordered by customers then you can use SelectMany which automatically merges the products returned per each order:
public class Order
{
public List<Product> Products { get; set; }
// ....
}
IEnumerable<Product> results =
orders
.Where(o => o.Total > 10)
.SelectMany(o => o.Products);
The above returns a flatten sequence of products instead of an instance of IEnumerable<Product[]>. One other point worth mentioning here is that you need to ensure that o.Products is not null otherwise this operation may fail.
How to order by more than one element:
If you are using the query expression’s syntax then it is very easy:
var results =
from o in orders
orderby o.Total, o.Date
select o;
The way you achieve the same result using extension methods is slightly more complex. You will need to call the OrderBy method to order by the first element and returns an IOrderedEnumerable<T> instance. You can then use this to call ThenBy to order by the next element:
var results = orders
.OrderBy(o => o.Date)
.ThenBy(o => o.Total);
Hopefully you can see why you should not apply yet another OrderBy instead of ThenBy in here!
How about an Outer Join?
Joins similar to an equality based inner joins are provided by the “Join” extension method or through the “join” query expression clause.
On the other hand, Outer joins are not fully supported. The closest to a left outer join is perhaps provided by the GroupJoin extension method or the join ... into ... clause. Bear in mind that this does not return a flattened structure. It will be more of a tree structure in which non-leaf branches represent the items from the outer sequence and leaf branches would be the items from the inner sequence. See here for a sample.
Duplicates are not allowed in any set operations!
SET theory does not recognise duplicates. In fact in a set, you can never have the same item more than once. Therefore, as you would expect, all set based operations such as Distinct, Union, Intersect, and Except remove duplicates. By default duplicates are found based on their HashCode HashCode:
string[] names1 = new string[] {
"Pedram", "Pedram" };
string[] names2 = new string[] {
"James", "John" };
var result = names1.Union(names2);
result is a set of strings which only includes “Pedram”, “James” and “John”. No elements are repeated.
var result = names1.Except(names2);
result is now a set of strings that includes one single item: “Pedram”.
How to apply projection of grouping elements in a GroupBy:
When GroupBy is applied, the result is of type IEnumerable<IGrouping<Tkey, TSource>>. IGrouping itself is an IEnumerable<TSource> with one extra property: “Key”.
public interface IGrouping<TKey, TSource> :
IEnumerable<TSource>, IEnumerable
{
TKey Key { get; }
}
IGrouping is a sequence of grouping elements. In order to apply a projection on these elements, you can use an override of GroupBy which has an elementSelector property allowing you to specify a delegate to perform the projection:
var results =
orders
.GroupBy(o => o.Date, o => o.Name);
foreach (var orderGroup in results)
{
Console.WriteLine(orderGroup.Key.ToShortDateString());
foreach (string orderName in orderGroup)
{
Console.WriteLine("\t" + orderName);
}
}
Output:
07/10/2007
Order 3
Order 2
08/10/2007
Order 4
Order 1
...
GroupBy also has other overrides which allow you to specify a resultSelector function. The result selector allows you to fold each group into a single element of the type of the group’s key.
Comments
- Anonymous
November 08, 2007
ASP.NET DevConnections - The ASP.NET MVC Framework [Via: Scott Hanselman ] Official ASP.NET MVC framework...