.NET Strategies with Advanced Iteration Techniques for C# Collections
We aim to simplify complex control structures and minimize the need for complex branching logic in C#.
Building on the principles laid out in our previous article, where we examined strategies to streamline iterations in a matrix-based image processing scenario, we continue our exploration with handling collections.
The Pitfalls of Nested Collection Iterations
Consider a scenario with several nested collections, a pattern that often raises concerns in code reviews because it tends to make the code hard to manage:
List<Category> categories = GetCategories();
List<Product> products = GetProducts();
List<Order> orders = GetOrders();
foreach (var category in categories)
{
foreach (var product in products)
{
if (product.CategoryId == category.Id)
{
foreach (var order in orders)
{
if (order.ProductId == product.Id && order.Quantity > 5)
{
ProcessLargeOrder(order);
}
}
}
}
}
The deeply nested foreach
loops and if-statements in this example clearly indicate the need for a simpler approach.
1. Declarative Beauty with LINQ
LINQ (Language Integrated Query) proves its worthiness by allowing a straightforward specification of what we want rather than how it should be done:
List<Category> categories = GetCategories();
List<Product> products = GetProducts();
List<Order> orders = GetOrders();
var largeOrders = from category in categories
join product in products on category.Id equals product.CategoryId
join order in orders on product.Id equals order.ProductId
where order.Quantity > 5
select new { product, order };
foreach (var largeOrder in largeOrders)
{
ProcessLargeOrder(largeOrder.order);
}
Here we’ve restructured the code from nested to flat, maintaining the same functionality with a layout that’s more manageable.
2. Structured Control with the Iterator Pattern
The iterator pattern is a powerful tool for custom collection traversal, providing a clear separation of concerns.
public class LargeOrderIterator : IEnumerable<Order>
{
private readonly List<Category> categories;
private readonly List<Product> products;
private readonly List<Order> orders;
public LargeOrderIterator(List<Category> categories, List<Product> products, List<Order> orders)
{
this.categories = categories
this.products = products
this.orders = orders
}
public IEnumerator<Order> GetEnumerator()
{
foreach (var category in categories)
{
var categoryProducts = products.Where(p => p.CategoryId == category.Id);
foreach (var product in categoryProducts)
{
var largeOrders = orders.Where(o => o.ProductId == product.Id && o.Quantity > 5);
foreach (var order in largeOrders)
{
yield return order;
}
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
//
// Usage example
//
List<Category> categories = GetCategories();
List<Product> products = GetProducts();
List<Order> orders = GetOrders();
// The Custom Iterator
var largeOrderIterator = new LargeOrderIterator(categories, products, orders);
foreach (var order in largeOrderIterator)
{
ProcessLargeOrder(order);
}
The elegance of this pattern lies in its abstraction. It organizes categories, products, and orders, hiding the complexity behind a clean interface.
3. Encapsulation via Extension Methods
Extension methods provide a way to add new methods to existing types without modifying their source code.
This gives us a potent tool for decomposing complex control structures into comprehensible chunks:
using System;
using System.Collections.Generic;
using System.Linq;
public static class OrderProcessingExtensions
{
public static void Process(List<Order> orders, categories, products, processAction, quantityThreshold = 5)
{
foreach (var category in categories)
{
foreach (var product in products.Where(p => p.CategoryId == category.Id))
{
foreach (var order in orders.Where(o => o.ProductId == product.Id && o.Quantity > quantityThreshold))
{
processAction(order);
}
}
}
}
}
//
// Usage example
//
List<Category> categories = GetCategories();
List<Product> products = GetProducts();
List<Order> orders = GetOrders();
// Call the Extension Method List<Order>.Process()
orders.Process(categories, products, ProcessLargeOrder);
The Process()
extension method applied to List<Order>
introduces a layer of abstraction. It allows us to pass in the logic for processing orders, neatly encapsulating the iteration details.
Conclusion: Clean Code Principles in Practice
The code transformations highlighted in these examples illuminate the power of strategies that promote clean code principles.
By employing LINQ, we embraced clarity and expressiveness.
The iterator pattern exemplified the Single Responsibility Principle (SRP) and Separation of Concerns (SoC), managing the traversal of data collections without mixing in the business logic.
Extension methods illustrate the Don’t Repeat Yourself (DRY) principle and encapsulation, allowing us to extend functionality in a modular fashion.
These practices guide us to produce code that is not only functional but also readable, maintainable, and a joy to work with. To delve deeper into these principles and discover more ways to enhance your coding practice, take a journey through the insights available at Clean Code Principles.
Keep integrating these C# principles into your coding practices to build a codebase that’s both efficient and a shining example of great software engineering.
Happy Coding!