In this article, we are going to learn about Sorting and Filtering with LINQ. Language-Integrated Query, or LINQ for short, provides a robust set of language extensions that we can use to shape query results according to our current needs. LINQ not only allows for a type-safe way to retrieve data but also a means to sort or filter queried data in the same way.

Let’s see how we can use LINQ to get exactly the data we want!

## How Do We Sort in LINQ?

First off, what do we mean by sorting? Sorting allows us to order the elements of a data set by one or more specific attributes. Additionally, we can nest the sorting to order each element in the set by whatever priority we desire.

For our sorting examples, we will be using a list of custom `Shape` objects. So, let’s start with our `Shape` definition:

```public abstract record Shape
{
public int ShapeId { get; init; }
public string? ShapeType { get; protected init; }
public int ShapeWidth { get; init; }
public int ShapeHeight { get; init; }
public bool Is3D { get; protected init; }
}```

We also need to implement some derived records.

Let’s start with the `Rectangle`:

```public record Rectangle : Shape
{
public Rectangle() => ShapeType = nameof(Rectangle);
}```

Next, we need the `Square` record:

```public record Square : Shape
{
public Square() => ShapeType = nameof(Square);
}```

And finally the `Cone` record:

```public record Cone : Shape
{
public Cone()
{
ShapeType = nameof(Cone);
Is3D = true;
}
}```

Now with our `Shape` objects defined, we can initialize our example `List<Shape?>`:

```var shapeList = new List<Shape?>
{
new Cone {ShapeId = 1, Height = 3, Width = 1},
null,
new Square {ShapeId = 0, Height = 2, Width = 2},
new Rectangle {ShapeId = 2, Height = 4, Width = 6},
null,
new Square {ShapeId = 3, Height = 5, Width = 5}
};```

## LINQ Sorting Methods

To sort this data we will use one of LINQ’s seven sorting operators. With the exception of `Reverse()`, each of the sorting methods returns an `IOrderedEnumerable<T>`. One of the main advantages of using the LINQ sorting operators is the fact that they do not modify the underlying collection. We can think of them simply as returning a custom “view” over the data.

### Order and OrderDescending

Both `Order()` and `OrderDescending()` where added in .NET 7.0 and provide a simple way to view our data in sorted order. Each of these methods orders the objects based upon the object’s default comparer. `Order()` sorts the collection in ascending order (smallest to largest), and `OrderDescending()` orders the collection in descending order (largest to smallest).

One thing to note is that if our object does not provide a default ordering comparer or a default order can’t be computed, then both `Order()` and `OrderDescending()` will throw an `InvalidOperationException`. For clarity’s sake, let’s look at an example:

```public sealed class SampleNoOrderer
{
public string Id { get; } = Guid.NewGuid().ToString();
}

public void GivenObjectWithoutDefaultOrderer_WhenCallOrder_ThenThrowsInvalidOperationException()
{
var list = Enumerable.Range(0, 10).Select(i => new SampleNoOrderer()).ToList();

var action = () => list.Order();

action.Enumerating().Should().Throw<InvalidOperationException>();
}```

With this in mind, let’s go ahead and update our `Shape` record to handle this situation. The first step is to add the `IComparable<T>` interface to our record definition:

`public abstract record Shape : IComparable<Shape>`

Second, we need to implement the `CompareTo()` method:

```public int CompareTo(Shape? other)
{
if (ReferenceEquals(this, other)) return 0;

return other is null ? 1 : ShapeId.CompareTo(other.ShapeId);
}```

Here we define our default sort order based upon `ShapeId`. For a more detailed look at the `IComparable<T>` interface, be sure to check out our article Techniques for Sorting a List in C#.

Now that we’ve implemented the interface, let’s see `Order()` in action:

`shapeList.Order();`

Which produces the output:

```Null Shape
Null Shape
Square { ShapeId = 0, ShapeType = Square, Width = 2, Height = 2, Is3D = False }
Cone { ShapeId = 1, ShapeType = Cone, Width = 1, Height = 3, Is3D = True }
Rectangle { ShapeId = 2, ShapeType = Rectangle, Width = 6, Height = 4, Is3D = False }
Square { ShapeId = 3, ShapeType = Square, Width = 5, Height = 5, Is3D = False }```

For completeness, let’s check out `OrderDescending()`:

`shapeList.OrderDescending();`

And check the result again:

```Square { ShapeId = 3, ShapeType = Square, Width = 5, Height = 5, Is3D = False }
Rectangle { ShapeId = 2, ShapeType = Rectangle, Width = 6, Height = 4, Is3D = False }
Cone { ShapeId = 1, ShapeType = Cone, Width = 1, Height = 3, Is3D = True }
Square { ShapeId = 0, ShapeType = Square, Width = 2, Height = 2, Is3D = False }
Null Shape
Null Shape```

Here we see that the order of the returned `IOrderedEnumerable<Shape?>` is, as we would expect, the exact reverse of the one returned when calling `Order()` on our list.

### OrderBy and OrderByDescending

`OrderBy()` and `OrderByDescending()` order data based upon a provided key selection lambda matching the signature `Func<TKey, TSource>`. The collection will be sorted according to the default ordering of the selected key (i.e. the property selected by the lambda). Just as with `Order()` and `OrderDescending()`, `OrderBy()` and `OrderByDescending()` will throw an exception if the selected key does not define a default ordering.

Let’s take a look at `OrderBy()` in action:

`shapeList.OrderBy(s => s?.ShapeType);`

Which yields:

```Null Shape
Null Shape
Cone { ShapeId = 1, ShapeType = Cone, Width = 1, Height = 3, Is3D = True }
Rectangle { ShapeId = 2, ShapeType = Rectangle, Width = 6, Height = 4, Is3D = False }
Square { ShapeId = 0, ShapeType = Square, Width = 2, Height = 2, Is3D = False }
Square { ShapeId = 3, ShapeType = Square, Width = 5, Height = 5, Is3D = False }```

To accomplish the same but in descending order, we can use `OrderByDescending()`:

`shapeList.OrderByDescending(s => s?.ShapeType);`

After we do that, we can inspect the output:

```Square { ShapeId = 0, ShapeType = Square, Width = 2, Height = 2, Is3D = False }
Square { ShapeId = 3, ShapeType = Square, Width = 5, Height = 5, Is3D = False }
Rectangle { ShapeId = 2, ShapeType = Rectangle, Width = 6, Height = 4, Is3D = False }
Cone { ShapeId = 1, ShapeType = Cone, Width = 1, Height = 3, Is3D = True }
Null Shape
Null Shape```

### ThenBy and ThenByDescending

We can use `ThenBy()` and `ThenByDescending()` on any `IOrderedEnumerable<T>` to produce a subsequent ordering of a collection.

As with both `OrderBy()` and `OrderByDescending()`, we pass a key selection lambda into `ThenBy()` or `ThenByDescending()` which is then used for ordering:

`shapeList.OrderBy(s => s?.Is3D).ThenBy(s => s?.ShapeId);`

Here we first order by whether or not the shape is 3D. We next order by the `ShapeId`:

```Null Shape
Null Shape
Square { ShapeId = 0, ShapeType = Square, Width = 2, Height = 2, Is3D = False }
Rectangle { ShapeId = 2, ShapeType = Rectangle, Width = 6, Height = 4, Is3D = False }
Square { ShapeId = 3, ShapeType = Square, Width = 5, Height = 5, Is3D = False }
Cone { ShapeId = 1, ShapeType = Cone, Width = 1, Height = 3, Is3D = True }```

We also have the option of reversing the sort ordering by calling `ThenByDescending()`:

`shapeList.OrderBy(sl => sl?.Is3D).ThenByDescending(sl => sl?.ShapeId);`

Notice here how the `Cone` object remains at the end of the list, but the middle shapes are now listed in descending order by `ShapeId`:

```Null Shape
Null Shape
Square { ShapeId = 3, ShapeType = Square, Width = 5, Height = 5, Is3D = False }
Rectangle { ShapeId = 2, ShapeType = Rectangle, Width = 6, Height = 4, Is3D = False }
Square { ShapeId = 0, ShapeType = Square, Width = 2, Height = 2, Is3D = False }
Cone { ShapeId = 1, ShapeType = Cone, Width = 1, Height = 3, Is3D = True }```

### Reverse

The `Reverse()` method does exactly what we would expect, returning an `IEnumerable<T>` of the collection in reverse order:

`shapeList.AsEnumerable().Reverse();`

One thing to note here in our sample code is the addition of the call to `AsEnumerable()`. Because `shapeList` is a `List<T>` which defines a `Reverse()` method, we need to “convert” our list to an `IEnumerable` in order to call `Enumerable.Reverse()`.

Now let’s take a look at the output:

```Square { ShapeId = 3, ShapeType = Square, Width = 5, Height = 5, Is3D = False }
Null Shape
Rectangle { ShapeId = 2, ShapeType = Rectangle, Width = 6, Height = 4, Is3D = False }
Square { ShapeId = 0, ShapeType = Square, Width = 2, Height = 2, Is3D = False }
Null Shape
Cone { ShapeId = 1, ShapeType = Cone, Width = 1, Height = 3, Is3D = True }```

## How Do We Filter in LINQ?

Filtering extracts elements from the dataset based on specified criteria. LINQ accomplishes filtering with two filtering methods. Both of which return an `IEnumerable<T>`.

### Where

The `Where()` method is a powerful extension that allows us to filter the collection based on the criteria we specify:

`shapeList.Where(s => s?.Height < 4);`

Which produces the following output:

```Cone { ShapeId = 1, ShapeType = Cone, Width = 1, Height = 3, Is3D = True }
Square { ShapeId = 0, ShapeType = Square, Width = 2, Height = 2, Is3D = False }```

In this specific example, we filter out all `Shape` objects with `Height >= 4`. Or phrased in reverse, we return an `IEnumerable<Shape?>` containing only `Shape` objects with `Height < 4`.

The `Where()` method accepts a parameter of type `Func<TSource, Boolean>`. This means we can pass in either a lambda function or a method matching this signature. In our previous example, we passed in the lambda function: `s => s?.Height < 4)`. Now, let’s take a look at calling it with a method.

First, let’s define our method:

`public static bool FilterIs3DAndWidthLessThan3(Shape? s) => s is {Is3D: true, Width: < 3};`

Second, let’s see it in action:

`shapeList.Where(LinqFilteringMethods.FilterIs3DAndWidthLessThan3);`

And the result:

`Cone { ShapeId = 1, ShapeType = Cone, Width = 1, Height = 3, Is3D = True }`

### OfType

`OfType<TResult>()` is a unique extension method that filters the collection based on a specified type. It returns an `IEnumerable<TResult>`. All elements in the collection that are not of type `TResult` are removed from the result set.

Accordingly, this can be useful to ensure that all the elements in the collection are of the specified type:

`shapeList.OfType<Square>();`

Which produces:

```Square { ShapeId = 0, ShapeType = Square, Width = 2, Height = 2, Is3D = False }
Square { ShapeId = 3, ShapeType = Square, Width = 5, Height = 5, Is3D = False }```

## Using Extension Methods For Sorting and Filtering With LINQ

Custom static methods – extension methods – allow us to customize or add functionality to how we sort or filter `IEnumerable<TSource>` objects. LINQ itself is a collection of extension methods defined in the `Enumerable` class.

Let’s create an extension method to filter out null items from an `IEnumerable<T?>`:

```public static IEnumerable<T> FilterNotNull<T>(this IEnumerable<T?> source)
{
using var enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
if (enumerator.Current is null) continue;

yield return enumerator.Current;
}
}```

This method will lazily enumerate the `source` collection yield returning each non-null item.

Now let’s see the method in practice:

`shapeList.FilterNotNull();`

As we see here, we call our `FilterNotNull()` method just like we called the other LINQ sorting and filtering methods, which returns:

```Cone { ShapeId = 1, ShapeType = Cone, Width = 1, Height = 3, Is3D = True }
Square { ShapeId = 0, ShapeType = Square, Width = 2, Height = 2, Is3D = False }
Rectangle { ShapeId = 2, ShapeType = Rectangle, Width = 6, Height = 4, Is3D = False }
Square { ShapeId = 3, ShapeType = Square, Width = 5, Height = 5, Is3D = False }```

## Conclusion

In this article, we covered a few of the excellent ways to implement Sorting and Filtering with LINQ. We hope that using this information you will be able to sort and filter your data with LINQ to get exactly what you need out of each query!