Validating CSV headers against an expected list My application (legacy code) reads a CSV input file with an expected format with regards to Column names as follows:


1st column - Should say "Marker"
2nd column - Should say "Category"
3rd column ... to ... N columns - These are the actual data columns. Should have number starting with 1 and increasing by 1 going right.


In other words, 3rd column should have "1", 4th column header should have "2".

Example CSV file with 10 data columns (total of 12 columns in file):



Marker,Category,1,2,3,4,5,6,7,8,9,10
TODD,Sea,0.001062344,0.001931534,0.002334951,0.002738369,0.003141786,0.003545203,0.004788256,0.005917489,0.007025671,0.008078547
JAMIE,Fork,0.00133847,0.002433581,0.002941854,0.003450128,0.003958402,0.004466676,0.006032825,0.007455569,0.00885179,0.010178331
LENNY,PITCH,0.001686365,0.003066119,0.003706504,0.004346889,0.004987274,0.005627659,0.007600883,0.009393428,0.011152557,0.012823893


My code that does this validation (and it works) is as follows:



fileStream = new FileStream(delimitedFileName, FileMode.Open,
   FileAccess.Read, FileShare.Read);
csvStreamReader = new StreamReader(fileStream);
string headerLine = csvStreamReader.ReadLine();
var headers = headerLine.Split(delimiter, '\\');

if (!string.Equals(headers[0], "Marker", StringComparison.InvariantCultureIgnoreCase) 
|| (!string.Equals(headers[1], "Category", StringComparison.InvariantCultureIgnoreCase)))
{
    throw new Exception("Invalid file format. Please use template (MarkerTemplate.csv).");
}

for (int i = 2; i &lt; headers.Length; i++)
{
    if (!string.Equals(headers[i], (i-1).ToString(),
        StringComparison.InvariantCultureIgnoreCase))
    {
    throw new Exception("Invalid file format. Please use template (MarkerTemplate.csv).");
    }
}


I want to avoid doing it like this, in 2 steps.

Is there a way to do some sort of delegate or Action to do the 2nd section, i.e. validate that the data column names are correct, for a variable number of data columns?

I want to avoid doing it in 2 separate sections as shown above, and have duplicate throw new Exception code for essentially the same exception.
Move the data column validation into a function

bool invalidDataColumns(string[] headers) {
    int[] numbers = headers.Skip(2).Select(s =&gt; int.TryParse(s, out var n) ? n : 0).ToArray();
    return !(numbers[0] == 1 &amp;&amp; numbers.Zip(numbers.Skip(1), (previous, current) =&gt; current - previous).All(diff =&gt; diff == 1));
}


And call it within the if condition.

//...

if (!string.Equals(headers[0], "Marker", StringComparison.InvariantCultureIgnoreCase)
    || (!string.Equals(headers[1], "Category", StringComparison.InvariantCultureIgnoreCase))
    || invalidDataColumns(headers)
    ) {
    throw new Exception("Invalid file format. Please use template (MarkerTemplate.csv).");
}

//...


The condition could be reduced further by moving the entire thing into its own function as well

bool invalidHeaders(string[] headers) {
    return !string.Equals(headers[0], "Marker", StringComparison.InvariantCultureIgnoreCase)
        || (!string.Equals(headers[1], "Category", StringComparison.InvariantCultureIgnoreCase))
        || invalidDataColumns(headers);
}


which now simplifies the code to

//...

if (invalidHeaders(headers)) {
    throw new Exception("Invalid file format. Please use template (MarkerTemplate.csv).");
}

//...


Unit test used to verify expected behavior

[TestClass]
public class InvalidDataColumnsTests {
    [TestMethod]
    [DataRow("Marker,Category,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15", false)]
    [DataRow("Marker,Category,1,2,3,4,5,6,7,8,9,10", false)]
    [DataRow("Marker,Category,1,2,3,4,5", false)]
    [DataRow("Marker,Category,x,2,3,4,5,6,7,8,9,10", true)]
    [DataRow("Category,1,2,3,4,5", true)]
    public void Should_Validate_Columns(string headerLine, bool expected) {
        //string headerLine = "Marker,Category,1,2,3,4,5,6,7,8,9,10";
        string[] headers = headerLine.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
        invalidHeaders(headers).Should().Be(expected);

        ////Example Use case
        //if (invalidHeaders(headers)) {
        //    throw new Exception("Invalid file format. Please use template (MarkerTemplate.csv).");
        //}
    }

    bool invalidHeaders(string[] headers) {
        return !string.Equals(headers[0], "Marker", StringComparison.InvariantCultureIgnoreCase)
            || (!string.Equals(headers[1], "Category", StringComparison.InvariantCultureIgnoreCase))
            || invalidDataColumns(headers);
    }

    bool invalidDataColumns(string[] headers) {
        int[] numbers = headers.Skip(2).Select(s =&gt; int.TryParse(s, out var n) ? n : 0).ToArray();
        return !(numbers[0] == 1 &amp;&amp; numbers.Zip(numbers.Skip(1), (previous, current) =&gt; current - previous).All(diff =&gt; diff == 1));
    }
}