Can we use both Index and Name attributes for our properties with CsvHelper?

I am new to writing CSV files with CsvHelper API. I have read the Getting Started help topic.

I have a requirement to create a empty CSV file with just the headers defined. This is so that the user can start from this template file and populate with data.

I know how to manually create a text file and to do it manually myself. But since I have my class with properties for reading CSV I wondered if I could make use of it for doing the reverse?

I see an example there with WriteHeader so I have come up with:

public void CreateEmptyHistoryFile(string path)
{
    using (var writer = new StreamWriter(path))
    using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
    {
        csv.WriteHeader<GenericHistory>();
    }
}    

But I can’t find it documented if it is ok to add both these attributes to my properties:

[Index(1)]
[Name(“xxx”)]
Public string Yyy …

The linked article says:

Remember how we can't rely on property order in .NET? If we are writing a class that has a header, it doesn't matter, as long as we are reading using the headers later. If we want to position the headers in the CSV file, we need to specify an index to guarantee it's order. It's recommended to always set an index when writing.

  • When I am reading using the name attribute.
  • When I write the empty file with headers I logically want to present them in order (just for clarity).

Make sense? Can we mix using both attributes?


Update

I wonder if that advice was out of date about the order of properties?

I just ran my code and the output is in the write order (I have no Index attributes added).


Update

My class has 82 properties and I tried to add a Index attribute to each property. When I tried reading I got an exception:

  <LogEntry Date="2022-01-22 18:38:03" Severity="Exception" Source="MSAToolsLibrary.Importer.Importer.ImportHistoryFromGeneric" ThreadId="1">
    <Exception Type="CsvHelper.TypeConversion.TypeConverterException" Source="CsvHelper.TypeConversion.DefaultTypeConverter.ConvertFromString">
      <Message>The conversion cannot be performed.
    Text: 'Andrew Truckle'
    MemberType: System.Int32
    TypeConverter: 'CsvHelper.TypeConversion.Int32Converter'
IReader state:
   ColumnCount: 0
   CurrentIndex: 4
   HeaderRecord:
["Week","MidweekMeeting","WeekendMeeting","NumClasses","Host","Cohost","Chairman","AuxCounsellor1","AuxCounsellor2","PrayerOpen","Treasures_Name","Treasures_Theme","Treasures_Method","Gems_Name","Teaching","Teaching_Class1_Name","Teaching_Class2_Name","Teaching_Class3_Name","BibleReading_Study","StudentItem1_Study","StudentItem2_Study","StudentItem3_Study","StudentItem4_Study","StudentItem1_Description","StudentItem2_Description","StudentItem3_Description","StudentItem4_Description","BibleReading_Class1_Name","BibleReading_Class2_Name","BibleReading_Class3_Name","StudentItem1_Class1_Name","StudentItem1_Class1_Assistant","StudentItem2_Class1_Name","StudentItem2_Class1_Assistant","StudentItem3_Class1_Name","StudentItem3_Class1_Assistant","StudentItem4_Class1_Name","StudentItem4_Class1_Assistant","StudentItem1_Class2_Name","StudentItem1_Class2_Assistant","StudentItem2_Class2_Name","StudentItem2_Class2_Assistant","StudentItem3_Class2_Name","StudentItem3_Class2_Assistant","StudentItem4_Class2_Name","StudentItem4_Class2_Assistant","StudentItem1_Class3_Name","StudentItem1_Class3_Assistant","StudentItem2_Class3_Name","StudentItem2_Class3_Assistant","StudentItem3_Class3_Name","StudentItem3_Class3_Assistant","StudentItem4_Class3_Name","StudentItem4_Class3_Assistant","Living1_Name","Living1_Theme","Living1_Method","Living2_Name","Living2_Theme","Living2_Method","Living3_Name","Living3_Theme","Living3_Method","CBS_Conductor","CBS_Reader","PrayerClose","Weekend_Host","Weekend_Cohost","Weekend_Chairman","Weekend_PrayerOpen","Weekend_PT_Speaker","Weekend_PT_Theme","Weekend_PT_Number","Weekend_PT_Hospitality","Weekend_PT_Interpreter","Weekend_AT1_Name","Weekend_AT2_Name","Weekend_WT_Conductor","Weekend_WT_Reader","Weekend_WT_BibleVersesReader","Weekend_PrayerClose","Weekend_Misc"]
IParser state:
   ByteCount: 0
   CharCount: 2289
   Row: 2
   RawRow: 2
   Count: 82
   RawRecord:
27/01/2020,Y,Y,1,...

</Message>
      <StackTrace>   at CsvHelper.TypeConversion.DefaultTypeConverter.ConvertFromString(String text, IReaderRow row, MemberMapData memberMapData)
   at CsvHelper.TypeConversion.Int32Converter.ConvertFromString(String text, IReaderRow row, MemberMapData memberMapData)
   at lambda_method(Closure )
   at CsvHelper.Expressions.RecordCreator.Create[T]()
   at CsvHelper.CsvReader.&lt;GetRecords&gt;d__87`1.MoveNext()
   at MSAToolsLibrary.Importer.Importer.ImportHistoryFromGeneric()</StackTrace>
    </Exception>
  </LogEntry>

I have deleted the names in the data for privacy reasons. I notice it refers to current index 4 and performing a integer conversion. As you can see, that value is 1. In the reading class:

[Index(4)]
public int NumClasses { get => HistoryWeek.NumClasses; set => HistoryWeek.NumClasses = value; }

As I say, if I remove all the [Index(..)] my reading code still works. Yet fails if I re-introduce all the indexing values.


I have generally found that it will be in the same order as the properties are declared in your class. However, if you want to be certain they are in a particular order, I would use both.

Type.GetProperties Method

The GetProperties method does not return properties in a particular order, such as alphabetical or declaration order. Your code must not depend on the order in which properties are returned, because that order varies.

You can try this example and see that both the Name and Index attributes will work when using CsvWriter.

void Main()
{
    using (var csv = new CsvWriter(Console.Out, CultureInfo.InvariantCulture))
    {
        csv.WriteHeader<Foo>();
    }
}

public class Foo
{
    [Name("Identity")]
    [Index(1)]
    public int Id { get; set; }
    [Name("FirstName")]
    [Index(0)]
    public string Name { get; set; }
}