I recently had to put together some code to load new person objects onto a database with the input data coming from a CSV file. As there was only going to be one row for each person's data it made sense to replicate the row as a class with each data item split out into its own named and type safe property for use elsewhere in the code. One easy way to do this would be to simply cast the relevant string to the correct datatype and put it into the correct property individually which might look something like this:
string[] results = FillString.Split(','); if(results[0] <> "")Title = results[0]; if(results[1] <> "")Surname = results[1]; if(results[2] <> "")Forename = results[2]; if(results[3] <> "")MiddleName = results[3]; if(results[4] <> "")DoB = Convert.ToDateTime(results[4]);
Ugly Code!
As you can see, this approach is really very ugly and violates my coding sensibilities in the worst way as DRY principles are firmly thrown out the window!. There are other issues with this approach of course, maintaining the code moving forwards would be arduous for adding/removing data items and it bloats the class unnecesarrily. A much better way to do things would be to assign a location to each property using a custom attribute and then use reflection to get a list of all properties with said attribute and fill them dynamically - this would mean that as long as your location attributes are set correctly you can add and remove as many properties as you like and everything will strill run smoothly
Custom Attribute
A custom attribute is a decorator class that you can bind to a class, property or field to describe it in some way for use with reflection later. As we only need to hold one value (which will be the int location of the relevant information of each property) we can declare a very simple custom attribute class, here's the one that I've used:
[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Property, AllowMultiple = true)] public class ArrayLocation : System.Attribute { public int Location; public ArrayLocation(int location) { this.Location = location; } }
Having this declared somewhere accessible to the class that you'd like to use it on means that you can decorate your properties with a new instance of the custom attribute class and set the location value for each one. We'll use this value later to pull everything together.
[ArrayLocation(0)] public string Title { get; set; } [ArrayLocation(1)] public string Surname { get; set; } [ArrayLocation(2)] public string Forename { get; set; } [ArrayLocation(3)] public string MiddleName { get; set; } [ArrayLocation(4)] public DateTime DoB { get; set; }
Auto Assignment
We're definitely getting there, the next thing to do will be to set up a method to load a property value for a given object. As with the attribute declaration this should be somewhere accessible to every object that you want to use it so chose your location wisely. Here is the method that I've been using, you'll need to pass in the object that you'd like to apply the value to, the property info for the property you'd like to fill and in this instance we know that we're only going to load from a string so that's easy to pass in:
private void LoadPropIfValHeld(object ObjToApplyTo, PropertyInfo ReqProp, string StrValue) { if (StrValue == "") return; if (ReqProp.PropertyType == typeof(string)) { ReqProp.SetValue(ObjToApplyTo, StrValue); } else if (ReqProp.PropertyType == typeof(DateTime)) { ReqProp.SetValue(ObjToApplyTo, Convert.ToDateTime(StrValue)); } else if (ReqProp.PropertyType == typeof(Double)) { ReqProp.SetValue(ObjToApplyTo, Convert.ToDouble(StrValue)); } else if (ReqProp.PropertyType == typeof(int)) { ReqProp.SetValue(ObjToApplyTo, Convert.ToInt32(StrValue)); } }
Fill 'Er Up!
Now that we've got this far it's really not all that difficult to set up a method on the target class to grab a list of all relevant properties, strip out the attribute location value which enables you to grab the correct entry in your results array to pass to the loading method above. You'll note that I've made the method private as for my purposes I only want to be able to use this method on constructing the class but that's a design decision for you to make. Here's the code that I've used:
private void LoadFromString(string FillString){Type pType = this.GetType();List PropList = pType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => Attribute.IsDefined(prop, typeof(ArrayLocation))).ToList();string[] results = FillString.Split('|');foreach (var thisProp in PropList){ ArrayLocation pAttr = (ArrayLocation)thisProp.GetCustomAttribute(typeof(ArrayLocation), true); LoadPropIfValHeld(this, thisProp, results[pAttr.Location]); }}
That's that then!
With the above snippets you should be able to set up auto-loading of property information from your classes taking in lines from a CSV - happy coding!.