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:
- Performance Impact: Dynamic binding is slower than static binding because members must be resolved at runtime
- Compile-time Checking: Dynamic calls are not checked by the compiler, potentially introducing runtime errors
- IntelliSense Support: Dynamic members do not receive code completion support
- Type Safety: Loss of compile-time type safety guarantees
Recommended scenarios for using dynamic properties:
- When interacting with dynamic languages (such as Python, JavaScript)
- When processing data with unknown structures (such as JSON, XML deserialization)
- When implementing plugin systems or extension mechanisms
- 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.