Get particular cell value of CSV file


I have a sample csv file as below:

Index,Name,Age,Occupation
1,John,23,Driver
2,Jack,28,Painter
3,Alice,26,Accountant
4,Don,19,Student

And, here is the class for my csv file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FileHelpers;

namespace FileHelpersTester
{
    [DelimitedRecord(",")]
    [IgnoreFirst(1)]
    public class SpecDataOrder
    {
        public String Index;

        public String Name;

        public String Age;

        public String Occupation;
    }
}

My main method is as below:

namespace FileHelpersTester
{
    public class Program
    {
        static void Main(string[] args)
        {
            String specDataPath = @"C:\sample c# programs\FileHelpersTester\FileHelpersTester\bin\Debug\Sample.csv";
            String tester = GetValue(specDataPath, "2", SpecDataOrder.Name); //I expect the value of tester = "Jack"

        }

        public T GetValue<T>(String csvFilePath, String row, SpecDataOrder col)
        {
            var engine = new FileHelperEngine<SpecDataOrder>();
            var records = engine.ReadFile(csvFilePath);
            var dict = new Dictionary<String, SpecDataOrder>();

            foreach (var record in records)
            {
                dict[record.Index] = record;
            }
            return (T)Convert.ChangeType(dict[row].Index, typeof(T));
        }
    }    
}

I am not sure where I had gone wrong. This is just a test program that I had written before going into a much more complex code that handles a large number of data in csv format. Any help is highly appreciated.



One way to solve this is below. But this would change the signature of the method a bit getting the value. I am not sure if you can change that in your production code or not.

public static T GetValue<T>(String csvFilePath, int row, int col)
{
    string line = File.ReadLines(csvFilePath).Skip(row).Take(1).First();
    var x = line.Split(',');
    return (T)Convert.ChangeType(x[col-1], typeof(T));
}

There are a number of problems with this code.

  1. You are attempting to return a string as SpecDataOrder which is never going to work
  2. You are attempting to pass a member of an object as a name but it actually passes the value so expects an instance of the object
  3. You are assuming that the index field will always be "Index" but do this inside a generic function
  4. You are creating a dictionary collection of all records when you could return as soon as you find the correct index since you have a desire to only return on column
  5. You are trying to call an instance method from a static method

To fix these four issues, try using the following code:

    static void Main(string[] args)
    {
        String specDataPath = @"C:\sample c# programs\FileHelpersTester\FileHelpersTester\bin\Debug\Sample.csv";
        String tester = GetValue<SpecDataOrder>(specDataPath, "2", nameof(SpecDataOrder.Index), nameof(SpecDataOrder.Name)) as string; //I expect the value of tester = "Jack"

    }

    public static object GetValue<T>(String csvFilePath, object rowIndex, String indexName, String fieldName)
    {
        var engine = new FileHelperEngine(typeof(T));
        var records = engine.ReadFile(csvFilePath);

        var memberIndex = new PropertyOrFieldInfo<T>(indexName);
        var memberField = new PropertyOrFieldInfo<T>(fieldName);

        foreach (T record in records)
        {
            var indexValue = memberIndex.GetValue(record);
            {
                if (indexValue.Equals(rowIndex))
                {
                    return memberField.GetValue(record);
                }
            }
        }
        return null;
    }

Once you have that code in your Program.cs, you will also need to add the following class. Note that this class uses reflection to get the unknown field or property details that you have passed so it can get the value of that field/property when we have a row. Reflection does slow down your code, but helps with this generic nature.

public class PropertyOrFieldInfo<T>
{
    MemberInfo internalInfo;
    PropertyInfo internalProperty;
    FieldInfo internalField;

    public PropertyOrFieldInfo(string memberName)
    {
        internalProperty = typeof(T).GetProperty(memberName);
        if (internalProperty == null)
        {
            internalField = typeof(T).GetField(memberName);
            if (internalField == null)
                throw new MissingMemberException(typeof(T).FullName, memberName);
        }
    }

    public object GetValue(T source)
    {
        if (internalProperty != null)
            return internalProperty.GetValue(source);
        else
            return internalField.GetValue(source);
    }
}

If you are planning on a larger scale solution, keep your code for creating the datatable (but add your dictionary indexing code) and the returning a cell value in two different functions. That way you are not recreating the table/dictionary for every cell query.

Note that I haven't put code in here to check for null values etc. You should add that to ensure you don't call methods on a null object.