Skip to content

Instantly share code, notes, and snippets.

@sebastianknopf
Created January 29, 2026 08:16
Show Gist options
  • Select an option

  • Save sebastianknopf/162235534946bffc374550efb80aba00 to your computer and use it in GitHub Desktop.

Select an option

Save sebastianknopf/162235534946bffc374550efb80aba00 to your computer and use it in GitHub Desktop.
utility classes for reading and writing CSV files
using System.Text;
namespace CSV
{
public class CsvReader : IDisposable
{
public List<string> Headers { get; protected set; }
private StreamReader streamReader;
private char delimiter;
private bool hasHeaderRow;
public CsvReader(StreamReader streamReader, char delimiter = ',', bool hasHeaderRow = false)
{
this.streamReader = streamReader;
this.delimiter = delimiter;
this.hasHeaderRow = hasHeaderRow;
if (this.hasHeaderRow)
{
string[] headers = this.ReadHeaders();
if (headers != null)
{
this.Headers = new List<string>(headers);
}
}
if (this.Headers == null)
{
this.Headers = new List<string>();
}
}
public void Dispose()
{
this.streamReader.Close();
this.streamReader.Dispose();
}
public IEnumerable<string[]> ReadRecords()
{
var currentRow = new List<string>();
var currentCell = new StringBuilder();
var afterQuote = false;
var insideQuoteCell = false;
var readyToEndQuote = false;
var rowEndReached = false;
while (!this.streamReader.EndOfStream)
{
var character = (char)this.streamReader.Read();
if (insideQuoteCell)
{
if (afterQuote)
{
if (character == '"')
{
currentCell.Append("\"");
afterQuote = false;
}
else if (readyToEndQuote && character != '"')
{
afterQuote = false;
insideQuoteCell = false;
if (character == this.delimiter)
{
this.AddCell(currentRow, currentCell);
}
if (character == '\n')
{
rowEndReached = true;
}
}
else
{
currentCell.Append(character);
afterQuote = false;
}
readyToEndQuote = false;
}
else
{
if (character == '"')
{
afterQuote = true;
readyToEndQuote = true;
}
else
{
currentCell.Append(character);
}
}
}
else
{
if (character == this.delimiter)
{
this.AddCell(currentRow, currentCell);
}
else if (rowEndReached)
{
rowEndReached = false;
this.AddCell(currentRow, currentCell);
currentCell.Append(character);
yield return this.AddRow(ref currentRow);
}
else if (character == '\n')
{
this.AddCell(currentRow, currentCell);
yield return this.AddRow(ref currentRow);
}
else if (character == '"')
{
insideQuoteCell = true;
}
else
{
currentCell.Append(character);
}
}
}
if (currentRow.Count != 0 || currentCell.Length != 0)
{
this.AddCell(currentRow, currentCell);
yield return this.AddRow(ref currentRow);
}
}
private string[] ReadHeaders()
{
// call the IEnumerable only once by returning its first object immediately
// this lets the StreamReader stop at its current position and be able to read
// the remaining records using the ReadRecords() method
foreach (string[] headers in this.ReadRecords())
{
return headers;
}
return null;
}
private void AddCell(List<string> currentRow, StringBuilder currentCell)
{
currentRow.Add(currentCell.ToString().Trim('\r', '\"'));
currentCell.Clear();
}
private string[] AddRow(ref List<string> row)
{
string[] rowArray = row.ToArray();
row = new List<string>();
return rowArray;
}
}
}
namespace CSV
{
public class CsvWriter : IDisposable
{
public List<string> Headers { get; protected set; } = new List<string>();
private StreamWriter streamWriter;
private char delimiter;
private bool hasHeaderRow;
private bool headerRowWritten;
public CsvWriter(StreamWriter streamWriter, char delimiter = ',', bool hasHeaderRow = false)
{
this.streamWriter = streamWriter;
this.delimiter = delimiter;
this.hasHeaderRow = hasHeaderRow;
this.headerRowWritten = false;
}
public void Dispose()
{
this.streamWriter.Close();
this.streamWriter.Dispose();
}
public void WriteHeaders()
{
if (!this.headerRowWritten && this.hasHeaderRow)
{
this.streamWriter.WriteLine(string.Join(this.delimiter.ToString(), this.Headers.ToArray()));
this.headerRowWritten = true;
}
}
public void WriteRecords(List<string[]> rows)
{
this.WriteHeaders();
int lastRowIndex = rows.Count - 1;
for (int r = 0; r < rows.Count; r++)
{
List<string> columns = new List<string>(rows[r]);
List<string> elements = new List<string>();
foreach (string element in columns)
{
if (element != null && element.Contains(this.delimiter.ToString()))
{
elements.Add(string.Format("\"{0}\"", element));
}
else if (element != null)
{
elements.Add(element);
}
else
{
elements.Add("null");
}
}
if (r < lastRowIndex)
{
this.streamWriter.WriteLine(string.Join(this.delimiter.ToString(), elements));
}
else
{
this.streamWriter.Write(string.Join(this.delimiter.ToString(), elements));
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment