CSV reader in Windows Forms I have a csv parsing method in my Windows App that reads headers and information from the subsequent lines. I am not sure if I am doing in an efficient way. Please comment.

public List&lt;(string IPN, string FPTech)&gt; GetIPNFPTechFromCSV(string filePath)
    {
        List&lt;(string, string)&gt; IPNFPTechPairs = new List&lt;(string, string)&gt;();
        using (StreamReader csvfileReader = new StreamReader(filePath))
        {
            List&lt;string&gt; headings = new List&lt;string&gt;();
            if (HasHeaderRow)
            {
                headings = LoadFieldNamesFromHeaderRow(csvfileReader.ReadLine());
                if (headings.Count != 2)
                    return null;
                else
                {
                    int IPNPos = headings.IndexOf("PartNumber");
                    int FPPos = headings.IndexOf("FPTech");
                    if (IPNPos != -1 &amp;&amp; FPPos != -1)
                    {
                        string line;
                        while ((line = csvfileReader.ReadLine()) != null)
                        {
                            var IPNFPTechPair = (IPN: line.Split(Delimiter)[IPNPos], FPTech: line.Split(Delimiter)[FPPos]);
                            IPNFPTechPairs.Add(IPNFPTechPair);
                        }
                    }
                    else
                        return null;
                }
            }
            else
                return null;
        }
        return IPNFPTechPairs;
    }


In the caller I am doing

var InputPairs = inputcsvfile.GetIPNFPTechFromCSV(txt_filePath.Text);

            if (InputPairs == null)
            {
                MessageBox.Show("Please make sure input file is in valid format with PartNumber,FPTech Headers", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }


To me, it looks like I am returning nulls from too many paths in my csv parsing method. Please let me know how can I improve this code. I am also not sure how do we efficiently log or report eceptions though out the Windows App. Any ideas are welcome please.

Sample Input CSV File

FPTech,PartNumber
TK0,H19873-001
TK1,H19873-001
TK2,H19872-001
TK1,H19874-001
TK0,H19872-001


The input .csv should be in this format and the FPTech and PartNumber Headers can interchange and sometime smissing. When missing, any of these I want to report to user.
if (HasHeaderRow)
        {



It seems strange, that you know in advance, if the file has headers or not? If that is right, why then call the method in the first place if HasHeaderRow is false - when you do nothing in that case?




  public List&lt;(string IPN, string FPTech)&gt; GetIPNFPTechFromCSV(string filePath)



I would return IEnumerable&lt;..&gt; instead and then use yield return to return each data pair (see below).
I would define a struct or class instead of using a named tuple. IMO named tuples are only useful very locally, because they can be harder to maintain, if their definition changes and you distribute them across the application (see below).





  var IPNFPTechPair = (IPN: line.Split(Delimiter)[IPNPos], FPTech: line.Split(Delimiter)[FPPos]);



Here you split the same string twice. It would be better to do:

string[] cells = line.Split(Delimiter);
var IPNFPTechPair = (IPN: cells[IPNPos], FPTech: cells[FPPos]);




You should handle all the heading stuff in a separate method in order to make the design and responsibility more clear.



All in all I would do something like:

public IEnumerable&lt;IpnItem&gt; Read(string filePath)
{
  using (StreamReader reader = new StreamReader(filePath))
  {
    (int ipnColumn, int fptechColumn) = ReadHeaders(reader);

    string line;
    while ((line = reader.ReadLine()) != null)
    {
      string[] cells = line.Split(Delimiter);
      yield return new IpnItem(cells[ipnColumn], cells[fptechColumn]);
    }
  }
}

private (int ipnColumn, int fptechColumn) ReadHeaders(StreamReader reader)
{
  string[] headers = reader.ReadLine().Split(Delimiter);
  if (headers.Length != 2) throw new Exception("Number of column headers should be 2");
  (int ipnColumn, int fptechColumn) columnIndices = (Array.IndexOf(headers, "PartNumber"), Array.IndexOf(headers, "FPTech"));
  if (columnIndices.ipnColumn == -1 || columnIndices.fptechColumn == -1) throw new Exception("Invalid or missing column names");
  return columnIndices;
}


Where IpnItem is declared as:

  public class IpnItem
  {
    public IpnItem(string ipn, string fpTech)
    {
      IPN = ipn;
      FPTech = fpTech;
    }

    public string IPN { get; private set; }
    public string FPTech { get; private set; }
  }