Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using Reflection to Build Large Classes

0.00/5 (No votes)
28 Dec 2017 1  
Some classes end up with dozens of fields. Here's how to leverage Reflection to make data population code a snap.

Introduction

Sometimes, we find it necessary to build classes that contain dozens if not over a hundred separate data fields. It may be ugly, but sometimes it's still the best approach. By combining custom attributes with Reflection, we can eliminate the grunt work and a lot of boilerplate from writing and instantiating very large classes.

All you need for this approach to work is to get your data in key/values pairs (e.g. a URL query string).

Background

I developed this code while writing a class to store the data returned from a credit card transaction processing gateway. What they give you back can be considered a mini-data-dump. While perusing the vendor's documentation, I found an example of what they post back to my web server. After copying the list of fields into Excel, I discovered that it contains 100 distinct fields. Yes, 100! Since I don't have a clue how the data will be used downstream yet, I figured it would be best to make it possible to capture all of the data they sent. Since the class was going to contain 100 or more individual properties, I imagined that there has to be some way to make populating all the data a lot simpler.

Using the Code

The first thing I did was to define a custom attribute that I can use to decorate each property. The attribute contains a single string which just happens to be the key value of my data that is transmitted over-the-wire.

internal class TransactionRelayFieldAttribute : Attribute
{
  private string _fieldName;

  public TransactionRelayFieldAttribute(string fieldName)
  {
    _fieldName = fieldName;
  }

  public string Name { get { return _fieldName; } }
}

What this attribute allows me to do is to "decorate" all of the fields in my large class like this:

/// <summary>
/// Indicates if the transaction processed.
/// </summary>
/// <remarks>
/// Possible values;
/// 1 - Approved
/// 2 - Declined
/// 3 - Error
/// </remarks>
[TransactionRelayField("x_response_code")]    // here's the attribute
public string ResponseCode { get; set; }                // for some random field

In this example, I am able to provide a lot of the information necessary to figure out what this code is doing right in the code. This can be helpful for the next person who looks at your enormous class because they can see what it's all about right up front.

With my current project, I wrote out 100 hundred of these fields. The properties of the class alone took up nearly 800 lines of code.

Leveraging Reflection

By using reflection, I do not have to write out a lengthy function to process each possibility. Could you imagine a switch...case with 100 different paths. Whoever maintains this code after me would hunt me down and do bad things.

// I used static members and functions to create a property map
static Dictionary<string, PropertyInfo> _propertyMap;

// this function uses reflection to populate the map
private static MapProperties()
{
  _propertyMap = new Dictionary<string, PropertyInfo>();

  Type relayPost = typeof(TransactionRelay) // this is my large class

  foreach(PropertyInfo pInfo in relayPost.GetProperties())
  {
    foreach (object attr in pInfo.GetCustomAttributes())
    {
      var field = attr as TransactionRelayFieldAttribute;

      if (field != null)
      {
        _propertyMap.Add(field.Name, pInfo);
        break;
      }
    }
  }
}

// this gets called before any object of this class is created
static RelayPost()
{
  MapProperties();
}

Here, I used statics and Reflection to create and populate a property map of all 100 fields before the first object was ever created.

Next, when I need to instantiate an actual instance of this gigantic class, all I need is this function:

// this holds any values that don't have a key
private NameValueCollection _unhandledValues;

// if you don't want them to create this class directly, hide the constructor
private RelayPost() {}

public static RelayPost Create(NameValueCollection postValues)
{
  RelayPost post = new RelayPost();
  string key;
  string value;

  // create the collection that handles any unforeseen complications
  post._unhandledValues = new NameValueCollection();

  for (int i = 0; i < postValues.Count; ++i)
  {
    // get the key and value
    key = postValues.Keys[i];
    value = postValues[i];

    try
    {
      // here we use Reflection again to assign the value to the right property
      _propertyMap[key].SetValue(post, value);
    }
    catch (Exception)
    {
      post._unhandleValues.Add(key, value);
    }
  }

  return post;
}

And that's it. Populating a hundred fields was never so easy.

Putting it all together

So here is what it looks likes when you put it all together. I will write this in the context of my current project for clarity.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;

namespace Website.Models
{
  internal class TransactionRelayFieldAttribute : Attribute
  {
    private string _fieldName;
    
    public TransactionRelayFieldAttribute(string fieldName)
    {
      if (String.IsNullOrWhiteSpace(fieldName))
        throw new ArgumentNullException("fieldName");
        
        _fieldName = fieldName;
    }
    
    public string Name { get { return _fieldName; } }
  }
  
  public class RelayPost
  {
    private static Dictionary<string, PropertyInfo> _propertyMap;
    private NameValueCollection _unhandledKeys;
    
    private RelayPost() { }
    
    public NameValueCollection UnhandledKeys { get { return _unhandledKeys; } }
    
    /// <summary>
    /// Indicates if the transaction processed.
    /// </summary>
    /// <remarks>
    /// Possible values;
    /// 1 - Approved
    /// 2 - Declined
    /// 3 - Error
    /// </remarks>
    [TransactionRelayField("x_response_code")]
    public string ResponseCode { get; set; }
    
    /*
    
    And ninty-nine other fields that are part of the transaction.
    
    */
    
    static RelayPost()
    {
      CreatePropertyMap();
    }
    
    private static void CreatePropertyMap()
    {
      _propertyMap = new Dictionary<string, PropertyInfo>();
      
      Type relayPost = typeof(RelayPost);
      
      foreach (PropertyInfo pInfo in relayPost.GetProperties())
      {
        foreach (object attr in pInfo.GetCustomAttributes())
        {
          var field = attr as TransactionRelayFieldAttribute;
          
          if (field != null)
          {
            _propertyMap.Add(field.Name, pInfo);
            break;
          }
        }
      }
    }
    
    public static RelayPost Create(NameValueCollection postValues)
    {
      if (postValues == null)
        throw new ArgumentNullException("postValues");
        
      var post = new RelayPost();
      string key;
      string value;
      
      post._unhandledKeys = new NameValueCollection();
      
      for (int i = 0; i < postValues.Count; ++i)
      {
        key = postValues.Keys[i];
        value = postValues[i];
        
        try
        {
          _propertyMap[key].SetValue(post, value);
        }
        catch (Exception)
        {
          post._unhandledKeys.Add(key, value);
        }
      }
      
      return post;
    }
  }
}

Points of Interest

The thing I like about Reflection is that it's kind of like C++. You can accomplish all sorts of fun things with it but, on the other hand, it gives you enough rope to hang yourself. It gives you ways to short-cut all sorts of code but beware, your mileage may vary.

History

  • 12/22/2017: Initial posting
  • 12/26/2017: Minor syntax change. Added a more complete code example.
  • 12/28/2017: A few fixes in sample code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here