Implementing Dynamic Property Addition at Runtime in C#

Nov 24, 2025 · Programming · 9 views · 7.8

Keywords: C# | Dynamic Properties | ExpandoObject | DynamicObject | Reflection

Abstract: This article provides an in-depth exploration of two core methods for dynamically adding properties at runtime in C#: using ExpandoObject and custom DynamicObject derived classes. Through detailed analysis of reflection mechanisms, dynamic binding principles, and practical application scenarios, complete code examples and performance comparisons are provided to help developers choose the most appropriate dynamic property implementation based on specific requirements.

Background of Dynamic Property Requirements

In modern software development, there is often a need to dynamically add properties to objects at runtime rather than statically defining them at compile time. This requirement is common in scenarios such as data mapping, plugin systems, and configuration parsing. Traditional C# classes must determine all properties at compile time, which limits program flexibility.

ExpandoObject Solution

ExpandoObject is a dynamic type introduced in .NET Framework 4.0 that implements the IDynamicMetaObjectProvider interface, allowing dynamic addition and removal of members at runtime.

// Create dynamic object
dynamic dynamicObject = new ExpandoObject();

// Dynamically add properties
dynamicObject.SomeProperty = "Dynamic Property Value";
dynamicObject.NumberProperty = 42;

// Dynamically add methods
dynamicObject.DynamicMethod = new Action<string>(msg => Console.WriteLine(msg));

// Use dynamic members
dynamicObject.DynamicMethod(dynamicObject.SomeProperty);

Through the IDictionary<string, object> interface, properties can be added in bulk:

public static dynamic CreateDynamicObject(Dictionary<string, object> properties)
{
    var expando = new ExpandoObject();
    var dictionary = (IDictionary<string, object>)expando;
    
    foreach (var property in properties)
    {
        dictionary.Add(property.Key, property.Value);
    }
    
    return expando;
}

Custom DynamicObject Implementation

For more complex dynamic behavior control, you can inherit from the DynamicObject class to create custom dynamic objects:

public class CustomDynamicObject : DynamicObject
{
    private readonly Dictionary<string, object> _properties;

    public CustomDynamicObject(Dictionary<string, object> initialProperties)
    {
        _properties = new Dictionary<string, object>(initialProperties);
    }

    // Get all dynamic member names
    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return _properties.Keys;
    }

    // Handle property reading
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (_properties.TryGetValue(binder.Name, out result))
        {
            return true;
        }
        
        result = null;
        return false;
    }

    // Handle property setting
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _properties[binder.Name] = value;
        return true;
    }

    // Handle property deletion
    public override bool TryDeleteMember(DeleteMemberBinder binder)
    {
        return _properties.Remove(binder.Name);
    }
}

Practical Application Example

Below is a complete example of creating and using dynamic objects:

public static dynamic BuildDynamicObject(Dictionary<string, object> properties)
{
    return new CustomDynamicObject(properties);
}

// Usage example
public void DemonstrateDynamicUsage()
{
    var properties = new Dictionary<string, object>
    {
        ["UserName"] = "JohnDoe",
        ["Age"] = 30,
        ["IsActive"] = true,
        ["CreatedDate"] = DateTime.Now
    };

    dynamic userObject = BuildDynamicObject(properties);

    // Directly access dynamic properties
    Console.WriteLine($"User: {userObject.UserName}, Age: {userObject.Age}");
    
    // Dynamically modify properties
    userObject.Age = 31;
    userObject.Email = "john.doe@example.com";
    
    // Access via reflection
    var propertyNames = userObject.GetDynamicMemberNames();
    foreach (string name in propertyNames)
    {
        Console.WriteLine($"Property: {name}, Value: {userObject.GetType().GetProperty(name)?.GetValue(userObject)}");
    }
}

Performance Considerations and Best Practices

While dynamic types provide flexibility, they also introduce performance overhead and development challenges:

Recommended scenarios for using dynamic properties:

  1. When interacting with dynamic languages (such as Python, JavaScript)
  2. When processing data with unknown structures (such as JSON, XML deserialization)
  3. When implementing plugin systems or extension mechanisms
  4. During rapid prototyping phases

Extended Application: Combining Dynamic Objects with Reflection

Dynamic objects can be combined with reflection mechanisms to achieve more powerful runtime functionality:

public static void ProcessDynamicWithReflection(dynamic obj)
{
    // Get dynamic object type information
    Type dynamicType = obj.GetType();
    
    // Access dynamic properties via reflection
    var memberNames = obj.GetDynamicMemberNames();
    foreach (string memberName in memberNames)
    {
        try
        {
            var value = dynamicType.GetProperty(memberName)?.GetValue(obj);
            Console.WriteLine($"Member {memberName}: {value}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error accessing member {memberName}: {ex.Message}");
        }
    }
}

Conclusion

C# provides powerful dynamic programming capabilities, allowing flexible implementation of runtime dynamic properties through ExpandoObject and custom DynamicObject. Developers should balance flexibility with performance based on specific requirements and use these technologies in appropriate scenarios. For simple dynamic property needs, ExpandoObject is the preferred choice; for complex scenarios requiring custom behavior, inheriting from DynamicObject provides better control capabilities.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.