Reflecting on Reflection

by Matt 13. August 2010 16:24

It’s been a while since I’ve had to use reflection for anything, but it’s a very useful concept to understand as it can save you many lines of code and provide a smart solution when it is applicable.  After playing around with it for a little while, I finally did solve my reflection problem;  Apparently it works best when you stand in front of the mirror.

I have information in a database and I would like to represent it as an object.  I retrieved it using the generic ADO.NET methods we’ve all become quite familiar with.  (If you are wondering why these methods are static – I am copying them from a basic proof of concept console app.  You will definitely want to sort these into different tiers in any real life application!).

private static IList<Actuals> RetrieveActuals(int EmployeeId)
{
IList<Actuals> Actuals = new List<Actuals>();
string query = string.Format("select * from FunctionName('{0}')", EmployeeId);

using (var myConnection = new SqlConnection(connectionstring))
{
using (var sqlComm = new SqlCommand())
{
myConnection.Open();
sqlComm.Connection = myConnection;
sqlComm.CommandType = CommandType.Text;
sqlComm.CommandText = query;

using (var da = new SqlDataAdapter(sqlComm))
{
var ds = new DataSet();
da.Fill(ds, "FunctionName");
DataTable dt = ds.Tables[0];

foreach (DataRow r in dt.Rows)
{
Actuals a = (Actuals) Reflect(dt.Columns, r, new Actuals());
Actuals.Add(a);
}
}
myConnection.Close();
}
}
return Actuals;
}

All of this should be familiar to you, but something that you have probably noticed the call to the Reflect() method.  The parameters it takes are the DataTable’s ColumnCollection, the row, and a new instance of the custom Actuals class.  It is very important that the properties of this class match the column names in the database.  This is a case sensitive requirement.  Further, you must be sure that the datatypes of your class correspond accordingly with the datatypes of the database columns.  If you have a decimal column and you’re trying to assign it to a string property, expect a NullReferenceException.

The guts of the Reflect() method are probably more straightforward than you would otherwise guess.  First we get the type of the object on which you are reflecting the datarow, and then we iterate through the row columns to assign their values to the object’s properties using the InvokeMember() method on the Type class. 

public static Object Reflect(DataColumnCollection ColumnCollection, DataRow Row, Object Image)
{
string ErrorMessage;
Type t = Image.GetType(); //Used for reflection
for (int i = 0; i <= ColumnCollection.Count - 1; i++)
{
try
{ //NOTE the datarow column names must match the objects property names
t.InvokeMember(ColumnCollection[i].ColumnName, BindingFlags.SetProperty, null, Image, new object[] { Row[ColumnCollection[i].ColumnName] });
}
catch (Exception ex)
{ //Column doesn't exist or datatype is incorrect
ErrorMessage = ex.Message;
}
}
return Image;
}

Looking back up to the RetrieveActuals() method, you see that the returned objects are inserted into a List<T> object that is ultimately returned to the calling code.  That allows us to manipulate and present our data as we see fit.

Another case where reflection can be quite useful is when you have two objects of different types but which share some of the same properties.  Say you wish to create a super object which gets it’s data from several sources for the sole reason of a cleaner presentation layer.  Again, the property names and datatypes of each object must match exactly for the properties you wish to set via reflection.

Below I have a method called CreateLineItems().  This method accepts a strongly typed list of Actuals objects, compares the properties, and, if the property exists in both objects, populates the property in the LineItem object with the value of the same property in the  Actuals object.

public static IList<LineItem> CreateLineItems(IList<Actuals> Actuals)
{
IList<LineItem> LineItems = new List<LineItem>();
foreach (Actuals actuals in Actuals)
{
PropertyDescriptorCollection ActualsProperties = TypeDescriptor.GetProperties(actuals);
Type ReflectionObject = typeof (LineItem);
object lineitem = Activator.CreateInstance(ReflectionObject);

foreach (PropertyDescriptor p in ActualsProperties)
{
ReflectionObject.GetProperty(p.Name).SetValue(lineitem, p.GetValue(actuals), null);
}

LineItems.Add((LineItem) lineitem);
}

return LineItems;
}

The beauty of this is that even if you are not looking to iterate through the entire list, but instead you are looking for a specific Actuals in the list to assign to a specific LineItem object, you can change this method around quite easily.  Simply remove the iteration of the Actuals objects, and use the Where() extension method (defined in System.Linq) to pick the desired object:

Actuals actual = Actuals.Where(a => a.EMPL_NUM == EmployeeNumber).Single();

Then continue on by defining the PropertyDescriptionCollection and iterating through the object’s properties.

While it is true that applications that make use of reflection will take a little performance hit, anyone who uses this as an excuse to not even look into how reflection can solve their problem may be too embarrassed to admit they don’t understand it.  If you wish to measure the performance hit to your app, simply set a Stopwatch object to help determine if your apps performance is acceptable:

static void Main(string[] args)
{
Stopwatch stopwatch = Stopwatch.StartNew();
// Data Access...
// Reflection...
stopwatch.Stop();
Console.WriteLine(String.Format("All data retrieved and reflected in {0} seconds", stopwatch.Elapsed.TotalSeconds));
Console.Read();
}

Reflection is one of those concepts that most developers don’t have to utilize too often, and may even dread the thought of it having to work with.  The truth is, like any other concept, it’s actually pretty straightforward, and it allows you to utilize areas of the Framework which you may otherwise not be too familiar with.  Further, it makes your code stand out when contrasted to the work of those who avoid reflection (which is always great during peer code reviews).

Categories: .NET | C#