Although C# is a statically typed language, you can create classes that dynamically add and retrieve properties at runtime by using the dynamic keyword and the System.Dynamic.DynamicObject class.
This is an effective technique when handling schema-less data like JSON or when you want to flexibly manipulate data whose structure is not determined at compile time. In this article, I will explain how to implement a dynamic data class that uses a Dictionary as internal storage and allows access via property syntax (obj.Property = value).
Table of Contents
- Overview of DynamicObject
- Practical Code Example: Implementing a Dynamic Property Store
- Execution Result
- Technical Points
- Implementation of TrySetMember
- Implementation of TryGetMember
- Usage of dynamic variables
- Summary
Overview of DynamicObject
The DynamicObject class is a base class used to define the behavior of dynamic operations. By inheriting from this class and overriding the following methods, you can customize dynamic behavior:
- TrySetMember: Called when a value is assigned to a property.
- TryGetMember: Called when a value is retrieved from a property.
Practical Code Example: Implementing a Dynamic Property Store
The following code is an example of a DynamicPropertyStore class that inherits from DynamicObject and allows arbitrary properties to be added freely. Internally, it manages data using a Dictionary<string, object>.
using System;
using System.Collections.Generic;
using System.Dynamic;
namespace DynamicProgramming
{
class Program
{
static void Main()
{
// Instantiate as a dynamic type.
// This bypasses compiler type checking and performs member resolution at runtime.
dynamic userProfile = new DynamicPropertyStore();
// 1. Set properties dynamically (TrySetMember is called)
userProfile.FullName = "Ichiro Sato";
userProfile.Address = "Chiyoda-ku, Tokyo";
userProfile.BirthDate = new DateTime(1990, 5, 15);
userProfile.Age = 33;
// 2. Get property values (TryGetMember is called)
Console.WriteLine($"Name: {userProfile.FullName} ({userProfile.FullName.GetType().Name})");
Console.WriteLine($"Address: {userProfile.Address}");
Console.WriteLine($"BirthDate: {userProfile.BirthDate:yyyy/MM/dd}");
// 3. Using custom methods
// Even with dynamic types, standard methods can be called if cast,
// or you can design it to be called via an interface.
var store = (DynamicPropertyStore)userProfile;
if (store.HasProperty("Address"))
{
Console.WriteLine("The 'Address' property is defined.");
}
}
}
/// <summary>
/// A class where properties can be dynamically added and retrieved at runtime.
/// </summary>
public class DynamicPropertyStore : DynamicObject
{
// Dictionary to hold property names and values
private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
/// <summary>
/// Called when setting a value to a property.
/// </summary>
/// <param name="binder">Information about the member being set</param>
/// <param name="value">The value to set</param>
/// <returns>True if the operation was successful</returns>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
// Save the value in the dictionary using the property name as the key.
// If it exists, it overwrites; otherwise, it adds.
_properties[binder.Name] = value;
return true;
}
/// <summary>
/// Called when retrieving a value from a property.
/// </summary>
/// <param name="binder">Information about the member being retrieved</param>
/// <param name="result">The retrieved value (output argument)</param>
/// <returns>True if the value was successfully retrieved</returns>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
// Attempt to retrieve the value from the dictionary
return _properties.TryGetValue(binder.Name, out result);
}
/// <summary>
/// Checks if the specified property is defined.
/// </summary>
public bool HasProperty(string name)
{
return _properties.ContainsKey(name);
}
}
}
Execution Result
Name: Ichiro Sato (String)
Address: Chiyoda-ku, Tokyo
BirthDate: 1990/05/15
The 'Address' property is defined.
Technical Points
1. Implementation of TrySetMember
binder.Name contains the name of the accessed property (e.g., “FullName”). By using this as a key to update the internal Dictionary, we achieve dynamic property addition. Returning true informs the runtime that the “operation was performed successfully.”
2. Implementation of TryGetMember
When retrieving a value, you set the value to the out parameter result and return true. If the key does not exist in the dictionary and you cannot return a value, returning false will cause a RuntimeBinderException to be thrown by the caller (this is the default C# behavior).
3. Usage of dynamic variables
When using this class, the variable type must be declared as dynamic.
DynamicPropertyStore obj = new DynamicPropertyStore();
obj.Name = "Test"; // Compile error (Because DynamicPropertyStore has no Name property)
dynamic obj = new DynamicPropertyStore();
obj.Name = "Test"; // Works correctly (TrySetMember is called)
Summary
Inheriting from DynamicObject enables flexible object manipulation in C#, similar to Python or JavaScript. It becomes a powerful tool when you want to determine the structure of DTOs (Data Transfer Objects) dynamically or when interoperating with external scripting languages.
