Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fill struct with String[]?

Tags:

c#

I'm parsing a CSV file and placing the data in a struct. I'm using the TextFieldParser from this question and it's working like a charm except that it returns a String[]. Currently I have the ugly process of:

String[] row = parser.ReadFields();
DispatchCall call = new DispatchCall();
if (!int.TryParse(row[0], out call.AccountID)) {
    Console.WriteLine("Invalid Row: " + parser.LineNumber);
    continue;
}
call.WorkOrder = row[1];
call.Description = row[2];
call.Date = row[3];
call.RequestedDate = row[4];
call.EstStartDate = row[5];
call.CustomerID = row[6];
call.CustomerName = row[7];
call.Caller = row[8];
call.EquipmentID = row[9];
call.Item = row[10];
call.TerritoryDesc = row[11];
call.Technician = row[12];
call.BillCode = row[13];
call.CallType = row[14];
call.Priority = row[15];
call.Status = row[16];
call.Comment = row[17];
call.Street = row[18];
call.City = row[19];
call.State = row[20];
call.Zip = row[21];
call.EquipRemarks = row[22];
call.Contact = row[23];
call.ContactPhone = row[24];
call.Lat = row[25];
call.Lon = row[26];
call.FlagColor = row[27];
call.TextColor = row[28];
call.MarkerName = row[29];

The struct consists of all those fields being Strings except for AccountID being an int. It annoys me that they're not strongly typed, but let's over look that for now. Given that parser.ReadFields() returns a String[] is there a more efficient way to fill a struct (possibly converting some values such as row[0] needing to become an int) with the values in the array?

**EDIT:**One restriction I forgot to mention that may impact what kind of solutions will work is that this struct is [Serializable] and will be sent Tcp somewhere else.

like image 511
Corey Ogburn Avatar asked Aug 17 '12 15:08

Corey Ogburn


1 Answers

Your mileage may vary on whether it is a better solution, but you could use reflection and define an Attribute class that you use to mark your struct members with. The attribute would take the array index as an argument. Assigning the value from the right array element would then happen by using reflection.

You could define your attribute like this:

[AttributeUsage(AttributeTargets.Property)]
public sealed class ArrayStructFieldAttribute : Attribute
{
    public ArrayStructFieldAttribute(int index)
    {
        this.index = index;
    }

    private readonly int index;

    public int Index {
        get {
            return index;
        }
    }
}

This means the attribute can simply be used to associate an int value named Index with a property.

Then, you could mark your properties in the struct with that attribute (just some exemplary lines):

[ArrayStructField(1)]
public string WorkOrder { // ...

[ArrayStructField(19)]
public string City { // ...

The values could then be set with the Type object for your struct type (you can obtain it with the typeof operator):

foreach (PropertyInfo prop in structType.GetProperties()) {
    ArrayStructFieldAttribute attr = prop.GetCustomAttributes(typeof(ArrayStructFieldAttribute), false).Cast<ArrayStructFieldAttribute>().FirstOrDefault();
    if (attr != null) {
         // we have found a property that you want to load from an array element!
        if (prop.PropertyType == typeof(string)) {
            // the property is a string property, no conversion required
            prop.SetValue(boxedStruct, row[attr.Index]);
        } else if (prop.PropertyType == typeof(int)) {
            // the property is an int property, conversion required
            int value;
            if (!int.TryParse(row[attr.Index], out value)) {
                Console.WriteLine("Invalid Row: " + parser.LineNumber);
            } else {
                prop.SetValue(boxedStruct, value);
            }
        }
    }
}

This code iterates over all properties of your struct type. For each property, it checks for our custom attribute type defined above. If such an attribute is present, and if the property type is string or int, the value is copied from the respective array index.

I am checking for string and int properties as that's the two data types you mentioned in your question. even though you have only one particular index that contains an int value now, it's good for maintainability if this code is prepared to handle any index as a string or an int property.

Note that for a greater number of types to handle, I'd suggest not using a chain of if and else if, but rather a Dictionary<Type, Func<string, object>> that maps property types to conversion functions.

like image 102
O. R. Mapper Avatar answered Sep 18 '22 12:09

O. R. Mapper