VSLive! Blog

Industry Insights, Information, and Developer News

Blog archive

LINQ Methods You're Not Using (But Should Be)

I've been writing C# for decades and I still discover LINQ methods that make me think "where have you been all my life?" Just last week, I was doing a code review and saw someone write a 10-line loop that could've been replaced with a single LINQ call.

LINQ has been around since 2007 but there are still a bunch of methods that fly under the radar. Sure, everyone knows Where(), Select(), and FirstOrDefault(). But there's a whole pile of other methods that can save you time and make your code cleaner.

Let's dig into some LINQ methods that you probably aren't using but definitely should be.

Chunk() - Batch Processing Made Easy
I don't know about you but I constantly need to process collections in batches. Maybe it's sending emails 25 at a time to avoid rate limits, or processing database records in chunks to avoid timeouts. Before .NET 6, you'd write something like this:

public void ProcessInBatches(List<Customer> customers, int batchSize)
{
    for (int i = 0; i < customers.Count; i += batchSize)
    {
        var batch = customers.Skip(i).Take(batchSize).ToList();
        SendEmailBatch(batch);
    }
}

With Chunk(), this becomes:

public void ProcessInBatches(List<Customer> customers, int batchSize)
{
    foreach (var batch in customers.Chunk(batchSize))
    {
        SendEmailBatch(batch);
    }
}

Way cleaner, right? And Chunk() returns arrays, which is more efficient than the Skip/Take approach.

DistinctBy() and UnionBy() - Finally!
For years, getting distinct objects by a property meant either implementing IEqualityComparer or doing some GroupBy gymnastics. Check out this old-school approach:

// The old way - grouping and taking first
var uniqueCustomers = customers
    .GroupBy(c => c.Email)
    .Select(g => g.First())
    .ToList();

Now with DistinctBy():

// The new way - clean and obvious
var uniqueCustomers = customers.DistinctBy(c => c.Email).ToList();

Same deal with UnionBy(). If you want to combine two lists and remove duplicates based on a property:

var allCustomers = onlineCustomers
    .UnionBy(inStoreCustomers, c => c.CustomerId)
    .ToList();

MinBy() and MaxBy() - No More Sorting!
Here's something I used to see all the time - sorting an entire collection just to get the min or max item:

// Please don't do this
var oldestCustomer = customers
    .OrderByDescending(c => c.Age)
    .FirstOrDefault();

That sorts the entire collection (O(n log n)) when you just need one item! Use MaxBy() instead:

// Much better - O(n) operation
var oldestCustomer = customers.MaxBy(c => c.Age);

You also get MinBy() for the opposite direction. These methods return the actual object, not just the min/max value.

ExceptBy() and IntersectBy() - Set Operations on Objects
Let's say you have a list of customers and you want to find which ones haven't placed orders. The old way involved extracting IDs and doing lookups:

// The old way
var customerIds = customers.Select(c => c.Id).ToHashSet();
var orderCustomerIds = orders.Select(o => o.CustomerId).ToHashSet();
var inactiveIds = customerIds.Except(orderCustomerIds);
var inactiveCustomers = customers.Where(c => inactiveIds.Contains(c.Id));

With ExceptBy():

// The new way
var inactiveCustomers = customers
    .ExceptBy(orders.Select(o => o.CustomerId), c => c.Id);

IntersectBy() works the same way for finding items that exist in both collections.

TryGetNonEnumeratedCount() - Avoiding Multiple Enumeration
This one's a bit more advanced but super useful. Sometimes you need to check if you can get the count of a collection without enumerating it. This is especially helpful when working with database queries or other expensive operations:

public void ProcessItems(IEnumerable<Order> orders)
{
    // Try to get count without enumeration
    if (orders.TryGetNonEnumeratedCount(out int count))
    {
        // We got the count "for free" - the source was a List, array, etc.

        Console.WriteLine($"Processing {count} orders");
    }
    else
    {
        // It's a true enumerable (maybe from yield return or LINQ query)
        Console.WriteLine("Processing orders...");
    }
    
    foreach (var order in orders)
    {
        ProcessOrder(order);
    }
}

If the source is a List or array, it returns the count immediately. If it's a true enumerable (like from yield return), it returns false and you handle it accordingly.

Zip() - Combining Parallel Collections
Ever need to combine two collections element by element? Maybe you have a list of products and a list of prices that you need to combine:

// The old way with indexes
var products = GetProducts();
var prices = GetPrices();
var productPrices = new List<ProductPrice>();

for (int i = 0; i < products.Count && i < prices.Count; i++)
{
    productPrices.Add(new ProductPrice 
    { 
        Product = products[i], 
        Price = prices[i] 
    });
}

With Zip():

var productPrices = products
    .Zip(prices, (product, price) => new ProductPrice 
    { 
        Product = product, 
        Price = price 
    })
    .ToList();

.NET 6 also added an overload that produces tuples automatically:

// Creates tuples of (product, price)
var pairs = products.Zip(prices);

foreach (var (product, price) in pairs)
{
    Console.WriteLine($"{product.Name}: ${price}");
}

Real-World Example: Data Processing Pipeline
Let me show you how these methods work together. Let's say you're processing customer orders from multiple sources:

public void ProcessMonthlyOrders()
{
    var onlineOrders = GetOnlineOrders();
    var phoneOrders = GetPhoneOrders();
    
    // Combine orders, removing duplicates by order ID
    var allOrders = onlineOrders.UnionBy(phoneOrders, o => o.OrderId);
    
    // Get unique customers who ordered
    var activeCustomers = allOrders
        .DistinctBy(o => o.CustomerId)
        .Select(o => GetCustomer(o.CustomerId));
    
    // Find the highest value order
    var biggestOrder = allOrders.MaxBy(o => o.TotalAmount);
    
    // Process in batches of 50
    foreach (var batch in allOrders.Chunk(50))
    {
        ProcessOrderBatch(batch);
    }
    
    // Find customers who didn't order this month
    var allCustomers = GetAllCustomers();
    var inactiveCustomers = allCustomers
        .ExceptBy(activeCustomers.Select(c => c.Id), c => c.Id);
        
    SendReactivationEmails(inactiveCustomers);
}

Summary
LINQ keeps evolving and it's worth checking what's new with each .NET release. These methods aren't just syntactic sugar -- they're more readable, often more performant, and definitely less error-prone than hand-rolled alternatives.

Next time you find yourself writing a loop to process a collection, ask yourself: is there a LINQ method for this? The answer is probably yes.

The methods I covered here shipped with .NET 6 and later, so if you're on an older version, you might need to upgrade. But honestly, if you're still on .NET Framework or an old .NET Core version, you're missing out on a lot more than just these LINQ methods.

About the Author

Benjamin Day is a consultant, trainer and author specializing in software development, project management and leadership.

Posted by Benjamin Day on 09/24/2025


Keep Up-to-Date with Visual Studio Live!

Email Address*Country*
Please type the letters/numbers you see above.