How it works
A CSV delimiter can appear inside a quoted field, so String.Split on every comma is not enough. The sample parses one deliberately bounded row format: comma-separated fields, double-quoted fields, doubled quotes inside quoted content, and no embedded line breaks. Naming that scope prevents a teaching parser from being mistaken for every CSV dialect.
The parser reads unquoted and quoted fields through separate branches. Inside a quoted field, two consecutive quotes append one literal quote. A single closing quote ends the field and must be followed by a delimiter or the end of the row. The sample therefore produces three fields, including a,b as one value.
Malformed input is rejected rather than silently repaired. An unclosed quoted field, a quote inside an unquoted field, or unexpected text after a closing quote violates this parser's grammar. Those checks matter because permissive parsing can shift later values into the wrong columns while still producing plausible-looking strings.
This implementation intentionally rejects embedded newlines. A complete file-level CSV reader may need quoted multiline records, configurable delimiters, encoding decisions, size limits, and useful source locations. Use an established parser with the needed contract for production data rather than extending this example through ad hoc string replacements.
Practice with an empty row, a trailing comma, an empty quoted field, and a field containing several doubled quotes. Predict the field count and exact content. Add malformed examples separately and verify that they fail instead of returning a partially shifted record.
Finally, combine the parser with explicit column-count and numeric-field validation. Parsing a row into strings is only the first stage of an import. Keep structural grammar errors, wrong column counts, and invalid domain values distinct so a caller can report the actual problem and preserve the original row for correction.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
Console.WriteLine(string.Join("|", Parse("Ada,\"a,b\",\"say \"\"hi\"\"\"")));
static List<string> Parse(string line) {
var fields = new List<string>(); int index = 0;
while (true) {
var field = new StringBuilder();
if (index < line.Length && line[index] == '"') {
index++; bool closed = false;
while (index < line.Length) {
char c = line[index++];
if (c == '"') {
if (index < line.Length && line[index] == '"') { field.Append('"'); index++; }
else { closed = true; break; }
}
else if (c is '\r' or '\n') throw new FormatException("Single row only");
else field.Append(c);
}
if (!closed || (index < line.Length && line[index] != ',')) throw new FormatException("Invalid quoted field");
} else {
while (index < line.Length && line[index] != ',') {
char c = line[index++];
if (c is '"' or '\r' or '\n') throw new FormatException("Invalid unquoted field");
field.Append(c);
}
}
fields.Add(field.ToString());
if (index == line.Length) return fields;
index++;
}
}
Expected output
Ada|a,b|say "hi"
A comma is a delimiter only outside a quoted field; a doubled quote is field content.