Reflection based IEnumerable<T> TrimAll() function

| No TrackBacks

Sorry for the too-long delay in updating my blog.  As usual, I’ve been pretty busy with a lot of things lately!! … you know, with the new job, summer activities, reading books.. stuff you probably don’t care a lot about. 

Today’s entry is going to deal with a pretty snazzy function I wrote as an Extension method called TrimAll().  This extension method is used when I’m gathering data from a database that has a lot of CHAR(x) fields in it and I’d rather not deal with space padding cluttering my code or UI’s.

/// <summary>
/// Dictionary that serves as a cache for cached PropertyInfo[] arrays
/// </summary>
static Dictionary<Type, PropertyInfo[]> trimCache;
/// <summary>
/// This function trims all public properties on an Enumerable object 
/// except those specified as optional parameters
/// </summary>
/// <typeparam name="T">The type of object</typeparam>
/// <param name="sourceList">A list of items</param>
/// <param name="ignoredFields">Props that should not be trimmed</param>
/// <returns>A cleaned up list with all string padding removed</returns>
public static List<T> TrimAll<T>(this IEnumerable<T> sourceList, 
params string[] ignoredFields)
{        
  if (trimCache == null)
    trimCache = new Dictionary<Type, PropertyInfo[]>();
 
  //  The list that we will be returning
  List<T> rList = new List<T>();
 
  //  Added for performance
  PropertyInfo[] props = null;
 
  //  Try to get a value from the dictionary if it exists
  if (!trimCache.TryGetValue(typeof(T), out props)) 
  {
    //  Get the properties for the type
    props = typeof(T)
              .GetProperties()
              .Where(c => !ignoredFields.Contains(c.Name) && 
                c.CanWrite && 
                c.PropertyType == typeof(System.String))
              .ToArray();
    //  Add it to the array
    trimCache.Add(typeof(T), props);
  }
 
  //  Iterate over each item in the source list
  foreach (T obj in sourceList) 
  {
    //  Iterate over each of the filtered properties
    for (int i = 0; i < props.Length; i++) 
    {
      //  Get the value 
      string o = (string)props[i].GetValue(obj, null);
      //  If it's not null, .Trim() it, otherwise leave it alone
      if (o != null)
        (props[i]).SetValue(obj, o.TrimEnd(
                                   ' ', 
                                   '\t', 
                                   '\n', 
                                   '\v', 
                                   '\f', 
                                   '\r'), null);
    }
 
    //  Add to the list
    rList.Add(obj);
  }
 
  //  Return the list!
  return rList;
}

 

So, there you have it.  I did do a bit of editing to make this fit within the constraints of my blog theme, but you should get the idea.

Explanation:

1.  I created a Dictionary of type Dictionary<Type,PropertyInfo[]> to improve performance of the TrimAll function on subsequent calls.  The first call will grab all of the public string properties of the input enumerable object and cache the reflection for future uses.

2.  When the function is called, it checks to see if the type of the current object <T> has already been entered into the cache.  For this, I use the TryGetValue object on the Dictionary class.  This is a very handy function that will return true/false if the Key is present in the dictionary.  In this case, the Type of the object is the key.  If the value is not present in the dictionary, then it will be created and added to the dictionary.

3.  You’ll notice that I’m using LINQ to search through all of the public properties of the source type.  I am also filtering the list if there are any optional string parameters passed in for properties that you would not want filtered.. as well as making sure that the property is writable, and that it is a string.

4.  Once the PropertyInfo[] props object is populated it is added to the cache for future use.

5.  With the new array of PropertyInfo objects for the source list, I start iterating over each object in the list.  For each object, I iterate over each item in the props[] array.  If the value is not null I update the Property in the object with a Trimmed version of the string.  You’ll notice that I am using “TrimEnd” with the specific fields I want to have trimmed explicitly specified.  I’m doing this to save a little bit of processing time and make the function slightly more efficient.  If you look at the TrimEnd method in Reflector, you’ll notice that it would cause several more iterations over each character.. this way I am saving those extra cycles.

6.  Finally, the updated object is added to the storage List<T> (rList) and returned to the calling function.

I hope you find this as useful as I have.. it’s saved me quite a few headaches and it’s nice having an easy way to Trim all of the values from my database in one fell swoop.

Usage:

using(…DataContext dc = new …DataContext()) {
  // With a Stored Procedure
  var dataList = dc.spSomeSproc().TrimAll();
  //
  var dataList2 = myTable.Where(c => c.SomeVal == “SearchVal”).TrimAll();
}


One of the nice features of this function is that it saves you the extra step of converting your returned Enumerable sequence to a List using .ToList().  I don’t know about you, but I quite often find myself doing that.  Of course, if your database is “normal” and isn’t full of CHAR fields (vs. VARCHAR) then you won’t have to worry about this at all. 

Knowing that most people probably don’t worry about CHAR fields, I still thought this made a nice blog post given that I am a huge fan of Reflection and finding neat ways to solve problems using it.  As usual, YMMV.

Question:  Do you think there is anyway to optimize this method further ?

Thanks for reading!

P.S.  You can now find me on Twitter.  Link:  http://www.twitter.com/csharpbydesign

Matthew MacSuga

No TrackBacks

TrackBack URL: http://www.csharpbydesign.com/cgi-bin/mt/mt-tb.cgi/30

About this Entry

This page contains a single entry by Matthew M. published on July 15, 2009 8:50 PM.

LINQ-To-SQL: Updating Disconnected Entity Objects was the previous entry in this blog.

LINQBugging - Using LINQPad For WinForms Testing is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.