Back to: C# New Features Tutorials
Enhanced Collection Support with Params Modifier in C#
In this article, I will discuss Enhanced Collection Support with Params Modifier in C# with Examples. Please read our previous articles discussing C# 13 New Features Overview with Examples. The introduction of enhanced collection support with the params modifier in C# 13 has made working with various collection types easier. This feature improves flexibility by allowing different collection types in methods that traditionally only accepted arrays.
What is the params Modifier?
In C#, the params modifier allows a method to accept a variable number of arguments. This can be particularly useful when the exact number of arguments is unknown at compile time. It will enable passing a comma-separated list of arguments (or an array) as a parameter to a method. Under the hood, the compiler packs all arguments into a single array parameter so you can call the method with zero or more arguments without manually creating an array.
Example to Understand the params Modifier:
In the example below, within the PrintNumbers method, numbers is a params array that can accept any number of integers as input. The params modifier simplifies method calls where the number of parameters is variable. Now, you can pass any number of integers to this method.
namespace CSharp13NewFeatures { public class Program { static void Main(string[] args) { PrintNumbers(1, 2, 3, 4); Console.ReadKey(); } public static void PrintNumbers(params int[] numbers) { foreach (var number in numbers) { Console.WriteLine(number); } } } }
What is Enhanced Collection Support with the params Modifier?
In previous versions of C# (Prior to C# 13), the params modifier could only be used with arrays. However, C# 13 extends the functionality of the params modifier to support additional collection types, including:
- System.Span<T>
- System.ReadOnlySpan<T>
- Types implementing IEnumerable<T> with an Add method (e.g., List<T>, Queue<T>, etc.).
This enhancement provides a more flexible approach to handling different types of collections in a method, while still maintaining the simplicity of the params modifier.
How Do We Use Enhanced Collection Support with the params Modifier?
For a better understanding, please modify the previous example as follows. Now, the PrintNumbers method can accept IEnumerable<T> collections (such as List<T>, Queue<T>, etc.) or even Span<T> or ReadOnlySpan<T>. The significant difference is that instead of just passing arrays, you can pass any collection type that implements IEnumerable<T> or Span<T>.
namespace CSharp13NewFeatures { public class Program { static void Main(string[] args) { //Passing Integer Array var intArray = new int[] { 1, 2 }; PrintNumbers(intArray); //Directly Pass the Numbers PrintNumbers(1, 2, 3); //Passing List of Integers List<int> list = new List<int> { 1, 2, 3, 4 }; PrintNumbers(list); //Passing a Queue Queue<int> queue = new Queue<int>(); queue.Enqueue(4); queue.Enqueue(5); PrintNumbers(queue); Console.ReadKey(); } public static void PrintNumbers(params IEnumerable<int> collection) { foreach (var item in collection) { Console.Write($"{item} "); } Console.WriteLine(); } } }
Real-Time Example: Data Aggregation and Report Generation
Let’s assume we are working on a report generation system that takes multiple datasets (e.g., customer orders, inventory data, and sales performance) and generates a comprehensive report for analysis. These datasets can come in various collection types such as List<T>, Queue<T>, Dictionary<T, string>, and others. The goal is to process them all uniformly, using the params modifier’s flexibility.
In this example, we will process different datasets (Sales Data, Customer Feedback, and Inventory Levels) and generate a summary report. We will use the enhanced collection support with params to dynamically handle different collection types.
Key Requirements:
- Sales Data: A list of total sales numbers for each region.
- Customer Feedback: A list of ratings provided by customers for a product.
- Inventory Levels: A dictionary containing the stock levels of different products, where the key is the product quantity and the value is the product name.
We will pass these datasets into the same method using the params modifier, which can handle various collection types. The method will aggregate this data and generate a summary report. Please modify the Program class as follows. The following code is self-explained, so please read the comment lines for a better understanding.
namespace CSharp13NewFeatures { // Wrapper class for Sales Data public class SalesData { public List<int> Sales { get; set; } public SalesData(List<int> sales) { Sales = sales; } } // Wrapper class for Customer Feedback public class FeedbackData { public List<int> Feedback { get; set; } public FeedbackData(List<int> feedback) { Feedback = feedback; } } // Wrapper class for Inventory Data public class InventoryData { public Dictionary<int, string> Inventory { get; set; } public InventoryData(Dictionary<int, string> inventory) { Inventory = inventory; } } public class Program { static void Main(string[] args) { // Simulating sample data // Sales Data List<int> salesData = new List<int> { 1500, 2200, 1800, 1900 }; SalesData sales = new SalesData(salesData); // Customer Feedback List<int> customerFeedback = new List<int> { 5, 4, 4, 5, 3 }; FeedbackData feedback = new FeedbackData(customerFeedback); // Inventory Levels (using Dictionary<int, string> with quantity as the key) // Product ID (key) and product name (value) Dictionary<int, string> inventoryData = new Dictionary<int, string> { { 100, "Product A" }, { 50, "Product B" }, { 200, "Product C" }, { 30, "Product D" }, { 150, "Product E" } }; InventoryData inventory = new InventoryData(inventoryData); // Calling the GenerateRetailReport method with multiple datasets GenerateRetailReport(sales, feedback, inventory); } // Method to process and aggregate data from different collections public static void GenerateRetailReport(params object[] datasets) { try { if (datasets == null || datasets.Length == 0) { throw new ArgumentException("No data sets provided."); } Console.WriteLine("Retail Performance Report\n"); // Initialize aggregates int totalSales = 0; int totalFeedbackScore = 0; int totalProductsInStock = 0; int numberOfFeedbacks = 0; foreach (var dataset in datasets) { if (dataset == null) { throw new ArgumentNullException("One of the datasets is null."); } // Process Sales Data if (dataset is SalesData salesData) { foreach (var sale in salesData.Sales) { totalSales += sale; } Console.WriteLine("Processed Sales Data."); } // Process Customer Feedback else if (dataset is FeedbackData feedbackData) { foreach (var feedback in feedbackData.Feedback) { totalFeedbackScore += feedback; numberOfFeedbacks++; } Console.WriteLine("Processed Customer Feedback."); } // Process Inventory Data (using Dictionary<int, string>) else if (dataset is InventoryData inventoryData) { foreach (var item in inventoryData.Inventory) { totalProductsInStock += item.Key; // Sum the stock quantities (keys) } Console.WriteLine("Processed Inventory Levels."); } else { throw new InvalidOperationException("Unsupported dataset type."); } } // Generate the final summary report Console.WriteLine("\nSummary Report:"); // Calculate average feedback score double averageFeedback = numberOfFeedbacks > 0 ? (double)totalFeedbackScore / numberOfFeedbacks : 0; Console.WriteLine($"Total Sales: {totalSales} USD"); Console.WriteLine($"Average Customer Feedback: {averageFeedback:F1}/5"); Console.WriteLine($"Total Products in Stock: {totalProductsInStock}"); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } } } }
Code Explanation:
In the GenerateRetailReport method, the params object[] datasets parameter allows us to pass multiple collections of any type. This approach will enable us to work with different collection types without defining each dataset separately. Inside the method, we check the type of each dataset and process it accordingly:
- If the dataset is of type SalesData, it contains sales figures (List<int>), and we sum up all the sales amounts.
- If the dataset is of type FeedbackData, it contains customer feedback ratings (List<int>), and we calculate the average feedback score.
- If the dataset is of type InventoryData, it contains inventory stock levels (Dictionary<int, string>), where the key represents the quantity of each product and the value represents the product name, and we sum up the stock quantities.
Output:
Benefits of This Approach:
- Flexibility: We can now pass different collection types (e.g., SalesData, FeedbackData, InventoryData) to the same method using the params modifier. The method dynamically handles each dataset type based on its context.
- Scalability: If more collection types need to be added in the future (e.g., Stack<int>, LinkedList<int>, or custom types), the method can still handle them without modification.
- Clean and Concise: By wrapping the data in dedicated classes (SalesData, FeedbackData, InventoryData), the code is clearer and more maintainable. We avoid potential issues of confusion between datasets.
Real-Time Scenarios Where Enhanced Collection Support with the params Modifier Can Be Used
- Business Intelligence Reports: Generate reports based on data that could come in different formats (e.g., List<int>, Queue<int>, Span<int>). This is common when gathering data from multiple sources or APIs.
- Data Aggregation: In systems that need to combine data from multiple types of collections (sales figures, feedback data, product inventories), the params modifier with enhanced collection support allows for clean aggregation without manually converting each collection type.
- Analytics Dashboards: Create analytics platforms where data may come from various collection types, such as user logs (Queue<T>), monthly sales (List<T>), and real-time sensor data (Span<T>). The flexibility of the params modifier supports seamless integration of these data sources.
The enhanced collection support with the params modifier in C# 13 allows for greater flexibility when working with various collection types. You can now pass IEnumerable<T>, Span<T>, ReadOnlySpan<T>, or any other collection type that implements IEnumerable<T> to methods, providing better support for real-world scenarios. This new feature significantly simplifies the code and eliminates the need to convert collections to arrays manually before passing them to methods.
In the next article, I will discuss Thread Synchronization with Advanced Locks in C# with Examples. I explain Enhanced Collection Support with Params Modifier in C# in this article with Examples. I would like to have your feedback. Please post your feedback, questions, or comments about this article.