SamoanProgrammer
SamoanProgrammer

Reputation: 954

C# Linq OrderBy based on condition

I have a foreach loop that I want to change:

foreach (var line in lines.OrderBy(x=> x.ColA))

If a condition is met, then instead of ordering by ColA, I want to order by ColB.

I know this could be done like the following:

var orderLines = new List<OrderLines>();
if (condition)
    orderLines = lines.OrderBy(x => x.ColB).ToList();
else
    orderLines = lines.OrderBy(x => x.ColA).ToList(); ;

foreach (var line in orderLines)

But I am sure there is a more elegant solution.

Upvotes: 2

Views: 2822

Answers (4)

AlanK
AlanK

Reputation: 1974

That is probably about as good as it gets.

Remember that behind that lambda expression magic happens which (effectively) binds to a Comparer<T> where T depends on the type of the columns being compared.

To make this more terse might make it less efficient. Specifically converting and comparing strings makes it both slower and can get you into trouble (ints sort to 1,2,3,...10,11,... vs their strings to "1","10","11",..."19","2","20","21"...).

A "one-liner" is only elegant if it's behaviour is obvious, otherwise it is obfuscated.

Your code is fine. (IMO;-)

Upvotes: 2

Zephyr
Zephyr

Reputation: 313

As @AlanK have mentioned, the closest we can get for simplifying OrderBy would be something like:

Func<OrderLines, string> selector = (orderLine) => condition ? orderLine.ColB : orderLine.ColA;

List<OrderLines> orderLines = lines.OrderBy(selector);

provided both ColA and ColB are of same data type. Otherwise it wouldn't be efficient due to the overhead of data type conversion.

Upvotes: 0

Harald Coppoolse
Harald Coppoolse

Reputation: 30512

Several solutions.

(1) Don't do the ToList() before your foreach, only create the IEnumerable.

IEnumerable<OrderLines> orderLines = condition ?
    lines.OrderBy(orderLine => orderLine.ColB) :
    lines.OrderBy(orderLine => orderLine.ColA);

foreach(OrderLine orderlLine in orderLines) {...}

(2) If you will be using this on several locations, consider to create an extension method. This way your method looks like any other LINQ method. See extension methods demystified

public static IEnumerable<OrderLine> OrderBy(
    this IEnumerable<OrderLine> source,
    bool condition)
{
    return condition ?
    lines.OrderBy(orderLine => orderLine.ColB) :
    lines.OrderBy(orderLine => orderLine.ColA);
}

Usage:

If operator checks chexBox1, sort by colB, else sort by colA:

IEnumerable<OrderLine> lines = ...
foreach(var sortedOrderLine in lines.OrderBy(this.CheckBox1.IsChecked))
{
    ...
}

Because it is an extension method of IEnumerable<OrderLine>, you can even intertwine it with other LINQ methods:

var result = lines.Where(orderLine => orderLine.Date.Year >= 2020)
                  .OrderBy(this.checkBox1.IsChecked)
                  .Select(orderLine => new
                  {
                       Id = orderLine.Id,
                       Price = orderLine.Price,
                  });

But all in all, it doesn't save you a lot of code. The only advantage would be if you would use it in a lot of methods. In that case, a change in how you want to OrderBy condition would have to be changed in only one place. But again: if you expect to use it in one place, moving it to a separate method might not help readers to understand what happens.

Upvotes: 4

Karan
Karan

Reputation: 12629

Install NuGet System.Linq.Dynamic and you can pass property name as string to OrderBy like below.

Usage list.AsQueryable().OrderBy("PropertyName1 SortOrder, ropertyName SortOrder"). Where PropertyName will be ColA ColB. And SortOrder will be ASC DESC.

  1. Add using System.Linq.Dynamic;
  2. foreach (var line in lines.AsQueryable().OrderBy(condition ? "ColB" : "ColA")

For .Net Core install NuGet System.Linq.Dynamic.Core.

  1. Add using System.Linq.Dynamic.Core;
  2. foreach (var line in lines.AsQueryable().OrderBy(condition ? "ColB" : "ColA")

For a better practice rather than providing PropertyName as string use nameof(Class.Property) like in your case nameof(OrderLines.ColA). So in case you change ColA property it will show Builderror and you will not get run time exception.

  • foreach (var line in lines.AsQueryable().OrderBy(condition ? nameof(OrderLines.ColB) : nameof(OrderLines.ColA))

Upvotes: 0

Related Questions