Why Would I Create A Custom LINQ Operator?

Friday, February 13, 2009

Here are three different reasons:

  1. For an operation that doesn’t exist.
  2. For readability.
  3. For performance.

An example for reason #1 is Bart De Smet’s ForEach operator. While you are on Bart’s blog, you can read about the pros and cons of a ForEach in his comments.

An example for reason #2 would be a custom join operator. Let’s say we are joining an object collection of employees to an object collection of departments.

var employeeAndDepartments =
    employees.Join(departments,
                   employee => employee.DepmartmentID,
                   department => department.ID,
                   (employee, department) =>
                       new
                       {
                           Employee = employee,
                           Department = department
                       });

The Join operator with extension methods is a little unwieldy. You need three lambda expressions: one to specify the employee key, one to specify the department key,  and one to specify the result. To make the query itself a bit more readable you could define a custom Join operator that knows how to join employees and departments.

public static IEnumerable<EmployeeDepartmentDTO> Join(
             this IEnumerable<Employee> employees,
                  IEnumerable<Department> departments)
{
    return employees.Join(departments,
                          employee => employee.DepmartmentID,
                          department => department.ID,
                          (employee, department) =>
                              new EmployeeDepartmentDTO
                               {
                                   Employee = employee,
                                   Department = department
                               });
}                                             

Not pretty, but it drastically cleans up code in other places:

var employeeAndDepartments = employees.Join(departments);

Reason #3 is performance. Generally speaking, you'll write an operator for performance when you know something that LINQ doesn't know.A good example is in Aaron Erickson’s i40 (Indexed LINQ) library. i40 features an IndexableCollection type that can drastically increase the performance of LINQ to Object queries (think table scan versus index seek). Imagine having a huge number of objects in memory and you commonly query to find just one.

var subject = subjects.Where(subject => subject.ID == 42)
                      .Single();

With i40 you can create an index on the ID property.

var subjects = /* ... */
                .ToIndexableCollection()
                .CreateIndexFor(subject => subject.ID);
/* ... */
var subject = subjects.Where(subject => subject.ID == 42)
                      .Single();

If you are using the i40 namespace, you’ll get a special i40 Where operator that takes advantage of the indexes built into the IndexableCollection.

//extend the where when we are working with indexable collections!
public static IEnumerable<T> Where<T>
(
  this IndexableCollection<T> sourceCollection,
  Expression<Func<T, bool>> expr
)
{
    // ... source from IndexableCollectionExtension.cs
}

What custom operators have you made?


Comments
Andrew Friday, February 13, 2009
var temp = dc.EventReviewer
.Where(r => r.FacilityID == facilityID);

if (CheckBoxActive.Checked)
temp = temp.Where(r => r.ActiveFlag == 1);

var reviewer = temp.ToList();

The new:

var reviewers = dc.EventReviewer
.Where(r => r.CareFacilityID == facilityID)
.WhereIf(CheckBoxActive.Checked, r => r.ActiveFlag == 1)
.ToList();

The why:

There is virtually no way to in-line the if(condition) logic in the query which results in a much bigger body of code including a temporary variable which to me, adds to the lack of readability. WhereIf() works with both IQueryable (Linq to SQL) and IEnumerable (Linq to object) expressions. Hopefully enough said!

The behind the scenes how:

public static IEnumerable<TSource> WhereIf<TSource>(this IEnumerable<TSource> source, bool condition, Func<TSource, bool> predicate)
{
if (condition)
return source.Where(predicate);
else
return source;
}

And

public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool condition, Expression<Func<TSource, bool>> predicate)
{
if (condition)
return source.Where(predicate);
else
return source;
}


Brett Friday, February 13, 2009
I think .WhereIf() is my new best friend Andrew, thank you!
scott Friday, February 13, 2009
@Andrew: awesome!
PiersH Sunday, February 15, 2009
a small drop-in optimization:

public static IEnumerable<T> Skip<T> (this IList<T> source, int skipcount)
{
if (source == null)
throw new NullReferenceException ("source");

if (skipcount >= source.Count)
throw new ArgumentOutOfRangeException ("skipcount");

while (skipcount < source.Count)
yield return source [skipcount++];
}
Bryan Watts Monday, February 16, 2009
public static IEnumerable<Expression> ToConstants<T>(this IEnumerable<T> source)
{
return source.Select<T, Expression>(element => Expression.Constant(element));
}


public static ReadOnlyCollection<T> ToReadOnly<T>(this IEnumerable<T> source)
{
if(source == null)
{
throw new ArgumentNullException("source");
}

return source as ReadOnlyCollection<T> ?? new ReadOnlyCollection<T>(source.ToList());
}


public static string Join(this IEnumerable<string> source, string separator)
{
if(source == null)
{
throw new ArgumentNullException("source");
}

return String.Join(separator, source.ToArray());
}
Daniel Earwicker Monday, February 16, 2009
incrediblejourneysintotheknown.blogspot.com/...

Another example: find the message of the inner-most exception:

catch (Exception ex)
{
string msg = ex.Chain(x => x.InnerException).Last().Message;

// do something with msg
}
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!