Free Ebook cover C# Essentials: Building Console Apps with .NET

C# Essentials: Building Console Apps with .NET

New course

12 pages

Collections and Basic Data Processing

Capítulo 7

Estimated reading time: 6 minutes

+ Exercise

Why collections matter in console apps

Console apps often need to store multiple values entered over time: names, scores, prices, or commands. A collection is a container that holds many values of the same type so you can process them as a group: display them, search them, and compute summaries such as totals or averages.

In this chapter you will use two common collection types:

  • Arrays (T[]): fixed size once created. Great when you know the number of items up front.
  • List<T>: dynamic size. Great when you collect an unknown number of items from the user.

Arrays: fixed-size storage

An array has a length that cannot change after creation. You access elements by index (starting at 0). Arrays are efficient and simple when the number of items is known.

Create and fill an array

int[] scores = new int[3]; // length is 3, indices: 0..2
scores[0] = 10;
scores[1] = 20;
scores[2] = 15;

Iterate an array

You can iterate with for when you need the index, or foreach when you only need the values.

// for: index available
for (int i = 0; i < scores.Length; i++)
{
    Console.WriteLine($"Score #{i + 1}: {scores[i]}");
}

// foreach: value only
foreach (int s in scores)
{
    Console.WriteLine($"Score: {s}");
}

Searching in an array

For basic searching, a loop is often enough. Track whether you found the item and where.

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

int target = 20;
int foundIndex = -1;

for (int i = 0; i < scores.Length; i++)
{
    if (scores[i] == target)
    {
        foundIndex = i;
        break;
    }
}

if (foundIndex != -1)
    Console.WriteLine($"Found {target} at index {foundIndex}.");
else
    Console.WriteLine("Not found.");

Arrays do not support adding/removing items because their size is fixed. If you need to grow/shrink the collection, use List<T>.

List<T>: dynamic storage for user-driven input

List<T> grows as you add items. It supports common operations like add, remove, and search. This makes it ideal for interactive console apps where you do not know how many inputs the user will provide.

Core operations: Add, Remove, Search

var numbers = new List<int>();

// Add
numbers.Add(10);
numbers.Add(25);

// Search
bool has25 = numbers.Contains(25); // true
int indexOf25 = numbers.IndexOf(25); // 1

// Remove (first match)
numbers.Remove(10); // returns true if removed

// Remove at index
numbers.RemoveAt(0);

Notes:

  • Contains checks whether an item exists.
  • IndexOf returns the index or -1 if not found.
  • Remove removes the first matching value; RemoveAt removes by index.

Iterating a List<T>

var items = new List<string> { "apple", "banana", "pear" };

for (int i = 0; i < items.Count; i++)
{
    Console.WriteLine($"{i}: {items[i]}");
}

foreach (string item in items)
{
    Console.WriteLine(item);
}

Use Count for a list’s size (not Length).

Basic aggregation: sum, average, min, max

Aggregation means reducing many values into a single result. In console apps, you often compute totals and summary statistics from user input.

Compute sum and average

var values = new List<int> { 10, 20, 15 };

int sum = 0;
foreach (int v in values)
{
    sum += v;
}

double average = values.Count > 0
    ? (double)sum / values.Count
    : 0.0;

Console.WriteLine($"Sum: {sum}");
Console.WriteLine($"Average: {average:F2}");

Compute min and max

Initialize min and max from the first element (when the list is not empty), then update as you scan.

var values = new List<int> { 10, 20, 15 };

if (values.Count == 0)
{
    Console.WriteLine("No values.");
}
else
{
    int min = values[0];
    int max = values[0];

    foreach (int v in values)
    {
        if (v < min) min = v;
        if (v > max) max = v;
    }

    Console.WriteLine($"Min: {min}");
    Console.WriteLine($"Max: {max}");
}

Processing lists of strings

String collections are common for names, tags, and categories. Typical tasks include filtering, searching, and formatting output.

Search case-insensitively

Users may type different casing. Normalize both sides before comparing.

var names = new List<string> { "Alice", "bob", "CHARLIE" };
string query = "BoB";

bool found = false;
foreach (string n in names)
{
    if (n.Trim().Equals(query.Trim(), StringComparison.OrdinalIgnoreCase))
    {
        found = true;
        break;
    }
}

Console.WriteLine(found ? "Match found" : "No match");

Build a formatted, numbered list

var names = new List<string> { "Alice", "Bob", "Charlie" };

for (int i = 0; i < names.Count; i++)
{
    Console.WriteLine($"{i + 1,2}. {names[i]}");
}

The alignment component {i + 1,2} reserves 2 characters for the number, which helps columns line up.

Guided build: collect user entries, display them, compute summary statistics, print a report

You will build a small console workflow that collects numeric entries into a List<double>, then prints the entries and summary statistics (count, sum, average, min, max). This pattern appears in many real console utilities: logging measurements, tracking expenses, or recording scores.

Step 1: Create the list and collect entries

We will accept numbers until the user enters a blank line. Use double.TryParse to validate input and avoid crashes on invalid text.

var entries = new List<double>();

Console.WriteLine("Enter numbers (blank line to finish):");

while (true)
{
    Console.Write("> ");
    string? input = Console.ReadLine();

    if (string.IsNullOrWhiteSpace(input))
        break;

    if (double.TryParse(input, out double value))
    {
        entries.Add(value);
    }
    else
    {
        Console.WriteLine("Please enter a valid number.");
    }
}

Step 2: Display what was collected

Show a numbered list so the user can verify the captured data.

Console.WriteLine();
Console.WriteLine("Entries:");

if (entries.Count == 0)
{
    Console.WriteLine("(none)");
}
else
{
    for (int i = 0; i < entries.Count; i++)
    {
        Console.WriteLine($"{i + 1,2}. {entries[i],10:F2}");
    }
}

{entries[i],10:F2} prints the number with 2 decimals and aligns it in a 10-character field.

Step 3: Compute summary statistics

Compute all statistics in a single pass through the list. This is efficient and keeps the logic clear.

int count = entries.Count;

double sum = 0;
double min = 0;
double max = 0;

if (count > 0)
{
    min = entries[0];
    max = entries[0];

    foreach (double v in entries)
    {
        sum += v;
        if (v < min) min = v;
        if (v > max) max = v;
    }
}

double average = count > 0 ? sum / count : 0.0;

Step 4: Print a formatted report

Print a compact report that is easy to read. When there are no entries, keep the report valid and avoid misleading min/max values.

Console.WriteLine();
Console.WriteLine("Report");
Console.WriteLine("------");
Console.WriteLine($"Count  : {count}");
Console.WriteLine($"Sum    : {sum:F2}");
Console.WriteLine($"Average: {average:F2}");

if (count > 0)
{
    Console.WriteLine($"Min    : {min:F2}");
    Console.WriteLine($"Max    : {max:F2}");
}
else
{
    Console.WriteLine("Min    : (n/a)");
    Console.WriteLine("Max    : (n/a)");
}

Optional extension: remove an entry by value

Many console tools let the user correct mistakes. This snippet removes the first matching value and then you can re-run the report logic.

Console.Write("Enter a value to remove (or blank to skip): ");
string? removeInput = Console.ReadLine();

if (!string.IsNullOrWhiteSpace(removeInput) && double.TryParse(removeInput, out double toRemove))
{
    bool removed = entries.Remove(toRemove);
    Console.WriteLine(removed ? "Removed." : "Value not found.");
}

Optional extension: collect and process a list of labels

If your app also needs text entries (for example, expense categories), you can collect them into List<string> and do simple processing like trimming, skipping blanks, and searching.

var labels = new List<string>();

Console.WriteLine("Enter labels (blank line to finish):");
while (true)
{
    Console.Write("> ");
    string? input = Console.ReadLine();

    if (string.IsNullOrWhiteSpace(input))
        break;

    string cleaned = input.Trim();
    labels.Add(cleaned);
}

Console.WriteLine("Labels:");
for (int i = 0; i < labels.Count; i++)
{
    Console.WriteLine($"{i + 1}. {labels[i]}");
}

Now answer the exercise about the content:

In a console app that collects an unknown number of numeric inputs from the user, which approach best fits the need and why?

You are right! Congratulations, now go to the next page

You missed! Try again.

List<T> is designed for user-driven input because it can grow dynamically as items are added, while arrays have a fixed size once created.

Next chapter

Working with Files in .NET: Reading and Writing Data

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.