C# Programming · Lesson 7
Stepping through Collections — Programming Examples
Five runnable C# examples covering directories as file collections, hex output, enumerators with MoveNext and Current, indexers, and yield-based iterator blocks.
Example 1 — Hex-Dump a Folder of Files
A folder can be treated as a collection of files. DirectoryInfo.GetFiles() returns a FileInfo[] that foreach walks, and a BinaryReader reads each file 10 bytes at a time. The {0:D3} format pads the line number to three digits, and {0:X2} prints each byte as two hex digits. Run it with a folder name as a command-line argument, or with none to use the current folder.
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
string folder;
if (args.Length == 0)
{
folder = Directory.GetCurrentDirectory();
}
else
{
folder = args[0];
}
Console.WriteLine("Folder: " + folder);
FileInfo[] files = GetFileList(folder);
Console.WriteLine("Files found: " + files.Length);
foreach (FileInfo file in files)
{
Console.WriteLine();
Console.WriteLine(file.Name + " (" + file.Length + " bytes)");
DumpFirstLines(file, 2);
}
}
static FileInfo[] GetFileList(string folder)
{
FileInfo[] files = new FileInfo[0];
try
{
DirectoryInfo directory = new DirectoryInfo(folder);
files = directory.GetFiles();
}
catch (Exception e)
{
Console.WriteLine("Folder \"" + folder + "\" is invalid");
Console.WriteLine(e.Message);
}
return files;
}
static void DumpFirstLines(FileInfo file, int maxLines)
{
BinaryReader reader;
try
{
reader = new BinaryReader(file.OpenRead());
}
catch (Exception e)
{
Console.WriteLine("Can't read " + file.Name + ": " + e.Message);
return;
}
for (int line = 1; line <= maxLines; line++)
{
byte[] buffer = new byte[10];
int count = reader.Read(buffer, 0, buffer.Length);
if (count == 0)
{
break;
}
Console.Write("{0:D3} - ", line);
for (int i = 0; i < count; i++)
{
Console.Write("{0:X2}, ", buffer[i]);
}
Console.WriteLine();
}
reader.Close();
}
}
Example 2 — Step through an Assignment List with an Enumerator
foreach is a shortcut for the enumerator protocol. GetEnumerator() returns an object that tracks its own position, MoveNext() advances it and returns false after the last item, and Current reads the item at that position. Two enumerators on the same list move independently.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> assignments = new List<string> { "Quiz 7", "Lab 7", "Reading Check", "Project Draft" };
Console.WriteLine("Using foreach:");
foreach (string task in assignments)
{
Console.WriteLine(" " + task);
}
Console.WriteLine("Using an enumerator:");
var taskEnum = assignments.GetEnumerator();
while (taskEnum.MoveNext())
{
Console.WriteLine(" " + taskEnum.Current);
}
Console.WriteLine("Two enumerators, each with its own position:");
var first = assignments.GetEnumerator();
var second = assignments.GetEnumerator();
first.MoveNext();
first.MoveNext();
second.MoveNext();
Console.WriteLine(" first is on: " + first.Current);
Console.WriteLine(" second is on: " + second.Current);
first.MoveNext();
first.MoveNext();
bool more = first.MoveNext();
Console.WriteLine(" first.MoveNext() after the last item: " + more);
}
}
Example 3 — Look Up Grades by Name with an Indexer
An indexer is declared with this[...] and lets a class use square brackets the way an array does, but with a string key instead of an int. The set accessor adds a new name or replaces an existing score, and the get accessor returns -1 when the name is not found. Because the indexer holds only int values, no cast is needed when reading a grade.
using System;
class Program
{
static void Main()
{
Gradebook grades = new Gradebook(5);
grades["Avery"] = 91;
grades["Jordan"] = 78;
grades["Casey"] = 85;
Console.WriteLine("Jordan's grade: " + grades["Jordan"]);
grades["Jordan"] = 82;
Console.WriteLine("Jordan after the retake: " + grades["Jordan"]);
Console.WriteLine("Morgan's grade: " + grades["Morgan"]);
}
}
class Gradebook
{
private string[] _names;
private int[] _scores;
public Gradebook(int size)
{
_names = new string[size];
_scores = new int[size];
}
private int Find(string name)
{
for (int i = 0; i < _names.Length; i++)
{
if (_names[i] == name)
{
return i;
}
}
return -1;
}
private int FindEmpty()
{
for (int i = 0; i < _names.Length; i++)
{
if (_names[i] == null)
{
return i;
}
}
throw new Exception("Gradebook is full");
}
public int this[string name]
{
get
{
int index = Find(name);
if (index < 0)
{
return -1;
}
return _scores[index];
}
set
{
int index = Find(name);
if (index < 0)
{
index = FindEmpty();
_names[index] = name;
}
_scores[index] = value;
}
}
}
Example 4 — Build foreach-Ready Classes with yield return and yield break
An iterator block is a GetEnumerator() method that uses yield return, so C# writes the enumerator class for you. MeetingDays yields items from an array, Reminder yields values with no array at all, and LabScores uses yield break to stop the sequence at the first ungraded lab.
using System;
using System.Collections;
class Program
{
static void Main()
{
MeetingDays days = new MeetingDays();
Console.WriteLine("IST 2373 meets on:");
foreach (string day in days)
{
Console.WriteLine(" " + day);
}
Reminder reminder = new Reminder();
foreach (string chunk in reminder)
{
Console.Write(chunk);
}
Console.WriteLine();
LabScores labs = new LabScores();
Console.WriteLine("Graded labs:");
foreach (int score in labs)
{
Console.WriteLine(" " + score);
}
}
}
class MeetingDays
{
string[] days = { "Monday 9:00", "Wednesday 9:00", "Friday 9:00" };
public IEnumerator GetEnumerator()
{
foreach (string day in days)
{
yield return day;
}
}
}
class Reminder
{
public IEnumerator GetEnumerator()
{
yield return "Lab 7 ";
yield return "is due ";
yield return "Friday.";
}
}
class LabScores
{
// -1 marks the first lab that hasn't been graded yet.
int[] scores = { 95, 88, 100, -1, 0, 0 };
public IEnumerator GetEnumerator()
{
foreach (int score in scores)
{
if (score < 0) yield break;
yield return score;
}
}
}
Example 5 — Named Iterators, an Endless Iterator, and an Iterator Property
An iterator block can also be a method with any name that returns IEnumerable, and it can take parameters. EvenSeatsDown computes its values instead of storing them, TicketNumbers never ends on its own so the foreach uses break, and StudyHours is a property whose get accessor yields values.
using System;
using System.Collections;
class Program
{
static void Main()
{
SeatingChart chart = new SeatingChart();
Console.Write("Even seats from 15 down to 5:");
foreach (int seat in chart.EvenSeatsDown(15, 5))
{
Console.Write(" " + seat);
}
Console.WriteLine();
Console.Write("First five ticket numbers:");
foreach (int ticket in chart.TicketNumbers())
{
if (ticket > 1005) break;
Console.Write(" " + ticket);
}
Console.WriteLine();
double total = 0;
int count = 0;
foreach (double hours in chart.StudyHours)
{
total += hours;
count++;
}
Console.WriteLine("Average study hours: " + (total / count).ToString("F1"));
}
}
class SeatingChart
{
double[] studyHours = { 2.5, 4.0, 3.5, 6.0 };
// A named iterator that computes its values.
public IEnumerable EvenSeatsDown(int top, int stop)
{
if (top % 2 != 0)
{
top -= 1;
}
for (int seat = top; seat >= stop; seat -= 2)
{
yield return seat;
}
}
// An iterator with no end; the caller decides when to stop.
public IEnumerable TicketNumbers()
{
for (int ticket = 1001; ; ticket++)
{
yield return ticket;
}
}
// A property whose get accessor is an iterator block.
public IEnumerable StudyHours
{
get
{
foreach (double hours in studyHours)
{
yield return hours;
}
}
}
}
Course Notes