Created
January 29, 2026 08:16
-
-
Save sebastianknopf/162235534946bffc374550efb80aba00 to your computer and use it in GitHub Desktop.
utility classes for reading and writing CSV files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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