Time to Reflect… How to add properties to a C# object dynamically at runtime

Have you been searching for a way to dynamically add properties to an object at runtime using C#?

If so, you may have come across how to do this using ExpandoObject. However, after some further research, perhaps you’ve determined that it’s not what you are really looking for.

For example, you might be trying to data-bind a list of objects to a particular type of user interface control which does not support dynamic model binding. What you really need is an object instance containing dynamic properties which appear as if they were part of the object at the time of compilation.

In this article, I discuss an alternative approach for your situation by leveraging the .NET Reflection ‘Emit’ API.

Use cases

It’s quite common for an application to support custom properties for resources, to help cater to the unique needs of the end-user.

The canonical example for this is a product entity which is comprised of a standard set of properties, representing the attributes which apply to the majority of sectors. However, across the many possible industry ‘verticals’, a product will tend to look very different and may require an ‘Author’ property for bookshops or a ‘Size’ property for clothing outlets.

In this scenario, a user interface would be required to allow the additional properties for the product to be defined. For example, the Name and Type of the property and perhaps its Required status, amongst other things.

A further user interface would be required to allow the user to specify the value of each additional property and to associate the values with the product entity whenever it is saved.

There may be other use cases, but this is the most useful one that I have encountered.

ExpandoObject?

Before I introduce you to the alternative solution, let me remind you of how ExpandoObject works.

If you haven’t come across ExpandoObject before, below is a short description of it, taken from the Microsoft Docs.

Represents an object whose members can be dynamically added and removed at run time.

To create an ExpandoObject simply ‘new it up’ and assign its return value to a dynamic variable.

dynamic todo = new ExpandoObject();

Now that the ExpandoObject has been created you can add properties to it, as follows.

todo.Id        = 1;
todo.UserId    = 1;
todo.Title     = "Buy Milk";
todo.Completed = false;

You can then proceed to add additional members anywhere in the codebase which has access to the todo variable. This includes properties, event handlers and methods.

todo.Important = true;
todo.Notes     = "2 for £2.20";
todo.ToString  = (Func<string>)(() => $"Title: {todo.Title}");

ExpandoObject is very flexible and is great for ‘interoping’ with other languages and COM objects. However, creating a list of dynamic objects and trying to bind this to a control doesn’t tend to work so well. In many cases a lot of extra code is needed to make dynamic objects bindable, taking away from the usefulness of the feature. Furthermore, on some occasions, the binding will not work at all.

Underneath, ExpandoObject implements the IDictionary<TKey,TValue> interface, so it’s really just a Dictionary class that holds a collection of keys and associated values.

Although ExpandoObject is quite clever in that it implements the INotifyPropertyChanged interface and can raise the PropertyChanged event whenever a member has been added, deleted or modified; this doesn’t guarantee that it will work correctly with your preferred controls library.

The alternative solution I’ll show you in the following section allows new types of objects with real properties to be created in memory while your program is running.

Code Emission

The key to the solution is the concept of emitting code into a dynamic assembly which is created at runtime.

This is achieved with the Reflection ‘Emit’ API. This API is typically used in advanced scenarios, such as when developing script engines or compilers. We’ll use a small subset of the API to achieve the desired end result.

Note that the code extracts below are taken from a sample project I’ve created on GitHub. I have included a note below the most important code snippets to indicate where they can be found within the sample project. This will help you visualise how the pieces of the solution are put together before looking at the project as a whole.

Let’s look at the Framework classes we need to work with. The first two, are as follows.

private readonly AssemblyBuilder _assemblyBuilder;
private readonly ModuleBuilder   _moduleBuilder;

Note that these are private readonly fields located within a class named DynamicTypeFactory.

AssemblyBuilder provides us with a way to create dynamic assemblies on the fly while our program is running.

All .NET assemblies are made up of at least one module. ModuleBuilder allows us to create one or more modules within dynamic assemblies.

We only need one assembly and module at a time, which we can create using the following code.

var uniqueIdentifier = Guid.NewGuid().ToString();
var assemblyName     = new AssemblyName(uniqueIdentifier);
 
_assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyNameAssemblyBuilderAccess.RunAndCollect);
_moduleBuilder   = _assemblyBuilder.DefineDynamicModule(uniqueIdentifier);

Note that this code is located within the constructor of DynamicTypeFactory.

We make sure that the assembly and module have a unique name by using a Guid as the name for both entities.

Notice that I’ve specified the RunAndCollect access mode for the assembly. This means that the dynamic assembly will be automatically unloaded and its memory will be reclaimed whenever it’s no longer accessible.

There is one more Framework class that we need to be aware of.

private TypeBuilder _typeBuilder;

Note that this is a private field located within DynamicTypeFactory.

TypeBuilder gives us the ability to define new members and add them to an existing Type.

We use the ModuleBuilder instance to define a new Type for the TypeBuilder instance to work with.

_typeBuilder = _moduleBuilder.DefineType(parentType.Name + Guid.NewGuid().ToString(), TypeAttributes.Public);
_typeBuilder.SetParent(parentType);

Note that this code is located within a public method named CreateNewTypeWithDynamicProperties in DynamicTypeFactory. The parentType variable is an instance of theType of  the object we wish to extend.

We make sure that the new Type we are creating has a unique name using a Guid and set a parent to use as a starting point.

You may have noticed the hierarchical nature of the Framework classes. TypeBuilder depends on ModuleBuilder which in turn depends on AssemblyBuilder.

Now for the complicated bit… This is all of the code which is needed to add a new property to an existing Type.

Type   propertyType = typeof(string);
string propertyName = "Notes";
string fieldName    = $"_{propertyName.ToCamelCase()}";
 
FieldBuilder fieldBuilder = _typeBuilder.DefineField(fieldNamepropertyTypeFieldAttributes.Private);
 
// The property set and get methods require a special set of attributes.
MethodAttributes getSetAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
 
// Define the 'get' accessor method.
MethodBuilder getMethodBuilder     = _typeBuilder.DefineMethod($"get_{propertyName}"getSetAttributespropertyTypeType.EmptyTypes);
ILGenerator   propertyGetGenerator = getMethodBuilder.GetILGenerator();
propertyGetGenerator.Emit(OpCodes.Ldarg_0);
propertyGetGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
propertyGetGenerator.Emit(OpCodes.Ret);
 
// Define the 'set' accessor method.
MethodBuilder setMethodBuilder     = _typeBuilder.DefineMethod($"set_{propertyName}"getSetAttributesnullnew Type[] { propertyType });
ILGenerator   propertySetGenerator = setMethodBuilder.GetILGenerator();
propertySetGenerator.Emit(OpCodes.Ldarg_0);
propertySetGenerator.Emit(OpCodes.Ldarg_1);
propertySetGenerator.Emit(OpCodes.Stfld, fieldBuilder);
propertySetGenerator.Emit(OpCodes.Ret);
 
// Lastly, we must map the two methods created above to a PropertyBuilder and their corresponding behaviors, 'get' and 'set' respectively.
PropertyBuilder propertyBuilder = _typeBuilder.DefineProperty(propertyNamePropertyAttributes.HasDefault, propertyTypenull);
propertyBuilder.SetGetMethod(getMethodBuilder);
propertyBuilder.SetSetMethod(setMethodBuilder);
 
// Add a 'DisplayName' attribute.
var attributeType    = typeof(DisplayNameAttribute);
var attributeBuilder = new CustomAttributeBuilder(
    attributeType.GetConstructor(new Type[] { typeof(string) }), // Constructor selection.
    new object[] { "Task Notes" }, // Constructor arguments.
    new PropertyInfo[] { }, // Properties to assign to.                    
    new object[] { } // Values for property assignment.
    );
propertyBuilder.SetCustomAttribute(attributeBuilder);

Note that this code is located within a private method named AddDynamicPropertyToType within DynamicTypeFactory. I have hard-coded the values of propertyType and propertyName variables in the code extract for the sake of clarity. The ToCamelCase method is a custom extension method.

The code above creates a new string property called Notes with a backing field called _notes.

The backing field is created using an instance of FieldBuilder.

Instances of MethodBuilder are used to create the property get and set methods.

Instances of IlGetGenerator do the magic of emitting IL (Intermediate Language) code into the dynamic assembly.

The PropertyBuilder instance is used to associate the get and set property methods with the actual property.

Lastly, as a bonus, we add a DisplayNameAttribute to the property which has been generated. This can be very useful in a UI context e.g. for grid column header captions.

After all that we can now create the new Type using the following code.

Type extendedType = _typeBuilder.CreateType();

To create an instance of the type we can use standard Reflection code, as follows.

var extendedObject = Activator.CreateInstance(extendedType);

Now that we have an instance of the new type we can populate its members, again using Reflection.

extendedType.GetProperty($"{nameof(Todo.Title)}")
            .SetValue(extendedObject"Buy milk"null);

Lastly, we can now create a collection and add our extended objects to it. When the collection of objects is assigned to a control as a data-source the objects will behave just like statically compiled objects!

Using the Factory

In the sample project, I demonstrate how to use the DynamicTypeFactory to create new object Types with just a couple of statements, as per the code below.

// Create a new Type based on a 'Todo' with additional dynamic properties.
var factory      = new DynamicTypeFactory();
var extendedType = factory.CreateNewTypeWithDynamicProperties(typeof(Todo), dynamicProperties);
 
// Create an instance of the new extended Type.
var extendedObject = Activator.CreateInstance(extendedType);

The same instance of the Factory can be used to construct multiple new Types with different base Types and/or a different set of dynamic properties.

Extending the solution

To make the solution practical, a way of storing the definition of the additional properties will be needed.

I hinted at this in the Use cases section where I talked about being able to define the properties and also configure and save the values for them.

Below is an example of what the definition of a dynamic property could look like as a class in code.

/// <summary>
/// Represents the definition of a dynamic property which can be added to an object at runtime.
/// </summary>
public class DynamicProperty
{
    /// <summary>
    /// The Name of the property.
    /// </summary>
    public string PropertyName { getset; }
 
    /// <summary>
    /// The Display Name of the property for the end-user.
    /// </summary>
    public string DisplayName { getset; }
 
    /// <summary>
    /// The Name of the underlying System Type of the property.
    /// </summary>
    public string SystemTypeName { getset; }
 
    /// <summary>
    /// The underlying System Type of the property.
    /// </summary>
    [JsonIgnore]
    public Type SystemType => Type.GetType(SystemTypeName);
}

A structure like this could either be serialised to JSON, mapped to a database table or stored in a document database in some other format.

The associated values could be stored in a similar way, with the appropriate property names and types, along with an ID which links the values back to the parent object.

I have demonstrated a rudimentary way of achieving something like this in my sample Dynamic Properties GitHub Repository. The project also includes all of the code from this article along with the relevant models, extension methods and content files.

Combining the above with user interfaces to define and save the properties definitions and values would provide a slick solution which is very extensible.

I hope that you’ll find a suitable use case for what I’ve covered in this article.

Regardless, I’m sure you’ll agree that being able to generate IL on the fly is very cool!

Comments

This site uses Akismet to reduce spam. Learn how your comment data is processed.