Click here to Skip to main content
15,884,388 members
Articles / Programming Languages / C#
Tip/Trick

Using Reflection to Build Large Classes

Rate me:
Please Sign up or sign in to vote.
4.54/5 (12 votes)
28 Dec 2017CPOL3 min read 27.7K   20   18
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.

C#
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:

C#
/// <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.

C#
// 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:

C#
// 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.

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Professional Experience
Languages: C++, C#, VB, .Net, SQL
Systems: MSSQL Database Architecture, Server Admin, MS System Center Admin(Service Manager)
Web: Asp.Net, Ajax, Web design and deployment
Other: MS Access guru

Comments and Discussions

 
GeneralMy vote of 3 Pin
Henrique C.2-Jan-18 3:54
professionalHenrique C.2-Jan-18 3:54 
GeneralRe: My vote of 3 Pin
Foothill2-Jan-18 4:19
professionalFoothill2-Jan-18 4:19 
GeneralMy vote of 3 Pin
Abd El Moneim Mohamed31-Dec-17 3:15
Abd El Moneim Mohamed31-Dec-17 3:15 
GeneralRe: My vote of 3 Pin
Foothill2-Jan-18 3:10
professionalFoothill2-Jan-18 3:10 
GeneralRe: My vote of 3 Pin
Abd El Moneim Mohamed2-Jan-18 3:47
Abd El Moneim Mohamed2-Jan-18 3:47 
GeneralRe: My vote of 3 Pin
Henrique C.2-Jan-18 3:40
professionalHenrique C.2-Jan-18 3:40 
GeneralRe: My vote of 3 Pin
Abd El Moneim Mohamed2-Jan-18 3:47
Abd El Moneim Mohamed2-Jan-18 3:47 
QuestionMistake in title, incomplete example Pin
Сергій Ярошко27-Dec-17 21:26
professionalСергій Ярошко27-Dec-17 21:26 
AnswerRe: Mistake in title, incomplete example Pin
Foothill28-Dec-17 8:38
professionalFoothill28-Dec-17 8:38 
GeneralRe: Mistake in title, incomplete example Pin
Сергій Ярошко29-Dec-17 6:48
professionalСергій Ярошко29-Dec-17 6:48 
QuestionHow about a complete sample application? Pin
Doncp27-Dec-17 13:18
Doncp27-Dec-17 13:18 
AnswerRe: How about a complete sample application? Pin
Foothill28-Dec-17 8:34
professionalFoothill28-Dec-17 8:34 
QuestionEntityWorker.Core Pin
Alen Toma26-Dec-17 6:34
Alen Toma26-Dec-17 6:34 
AnswerRe: EntityWorker.Core Pin
Foothill26-Dec-17 8:19
professionalFoothill26-Dec-17 8:19 
GeneralRe: EntityWorker.Core Pin
Alen Toma26-Dec-17 9:05
Alen Toma26-Dec-17 9:05 
QuestionI had a similar problem... Pin
Marc Clifton23-Dec-17 4:43
mvaMarc Clifton23-Dec-17 4:43 
QuestionWhy? Pin
Nick Polideropoulos23-Dec-17 3:47
Nick Polideropoulos23-Dec-17 3:47 
AnswerRe: Why? Pin
Foothill23-Dec-17 4:18
professionalFoothill23-Dec-17 4:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.