Creating and Using Dynamic Objects in C#: From ExpandoObject to Custom Dynamic Types

Nov 20, 2025 · Programming · 14 views · 7.8

Keywords: C# | Dynamic Objects | ExpandoObject | DynamicObject | Runtime Binding

Abstract: This article provides an in-depth exploration of creating and using dynamic objects in C#, focusing on the application scenarios and implementation principles of the System.Dynamic.ExpandoObject class. By comparing the differences between anonymous types and dynamic objects, it details how ExpandoObject enables runtime dynamic addition of properties and methods. The article also combines examples of creating custom dynamic objects to demonstrate how to inherit the DynamicObject class for implementing more complex dynamic behaviors, offering complete solutions for developers to achieve ViewBag-like dynamic functionality in non-MVC applications.

Fundamental Concepts of Dynamic Objects

In C# programming, dynamic objects provide a mechanism to expose members (such as properties and methods) at runtime rather than compile time. This feature enables developers to create objects that interact with structures that don't match static types or formats. Typical application scenarios for dynamic objects include handling HTML Document Object Model (DOM), interacting with dynamic languages like IronPython, and replacing traditional static types in scenarios requiring flexible data structures.

Differences Between Anonymous Types and Dynamic Objects

When first encountering dynamic features, many C# developers might attempt to use anonymous types for similar functionality but often encounter limitations. As shown in the following code:

dynamic MyDynamic = new { A = "a" };
MyDynamic.A = "asd"; // Throws RuntimeBinderException

Anonymous types have read-only properties after creation and cannot be modified or have new properties added at runtime. This is precisely the core problem that dynamic objects aim to solve.

ExpandoObject: Simple Dynamic Object Solution

The System.Dynamic.ExpandoObject class provides the most straightforward implementation of dynamic objects. This class allows dynamic addition and removal of members at runtime, perfectly meeting requirements similar to ViewBag.

Basic Usage Example

The following code demonstrates the basic usage of ExpandoObject:

dynamic MyDynamic = new System.Dynamic.ExpandoObject();
MyDynamic.A = "A";
MyDynamic.B = "B";
MyDynamic.C = DateTime.Now;
MyDynamic.TheAnswerToLifeTheUniverseAndEverything = 42;

Adding Methods to Dynamic Objects

ExpandoObject supports not only properties but also dynamic addition of methods:

MyDynamic.MyMethod = new Func<int>(() => 
{ 
    return 55; 
});
Console.WriteLine(MyDynamic.MyMethod()); // Output: 55

Implementation of Custom Dynamic Objects

For more complex dynamic behavior requirements, custom dynamic objects can be created by inheriting from the DynamicObject class.

Creating ReadOnlyFile Dynamic Class

The following example shows a custom dynamic object that reads text file contents:

public enum StringSearchOption
{
    StartsWith,
    Contains,
    EndsWith
}

class ReadOnlyFile : DynamicObject
{
    private string p_filePath;
    
    public ReadOnlyFile(string filePath)
    {
        if (!File.Exists(filePath))
        {
            throw new Exception("File path does not exist.");
        }
        p_filePath = filePath;
    }
    
    public List<string> GetPropertyValue(string propertyName,
        StringSearchOption StringSearchOption = StringSearchOption.StartsWith,
        bool trimSpaces = true)
    {
        // Implement file search logic
        List<string> results = new List<string>();
        using (StreamReader sr = new StreamReader(p_filePath))
        {
            string line;
            while ((line = sr.ReadLine()) != null)
            {
                string testLine = line.ToUpper();
                if (trimSpaces) { testLine = testLine.Trim(); }
                
                switch (StringSearchOption)
                {
                    case StringSearchOption.StartsWith:
                        if (testLine.StartsWith(propertyName.ToUpper())) 
                            results.Add(line);
                        break;
                    case StringSearchOption.Contains:
                        if (testLine.Contains(propertyName.ToUpper())) 
                            results.Add(line);
                        break;
                    case StringSearchOption.EndsWith:
                        if (testLine.EndsWith(propertyName.ToUpper())) 
                            results.Add(line);
                        break;
                }
            }
        }
        return results;
    }
    
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = GetPropertyValue(binder.Name);
        return result != null;
    }
    
    public override bool TryInvokeMember(InvokeMemberBinder binder, 
        object[] args, out object result)
    {
        StringSearchOption searchOption = StringSearchOption.StartsWith;
        bool trimSpaces = true;
        
        if (args.Length > 0) 
            searchOption = (StringSearchOption)args[0];
        if (args.Length > 1) 
            trimSpaces = (bool)args[1];
            
        result = GetPropertyValue(binder.Name, searchOption, trimSpaces);
        return result != null;
    }
}

Using Custom Dynamic Objects

Example of creating and using custom dynamic objects:

dynamic rFile = new ReadOnlyFile(@"TextFile1.txt");
foreach (string line in rFile.Customer)
{
    Console.WriteLine(line);
}

foreach (string line in rFile.Customer(StringSearchOption.Contains, true))
{
    Console.WriteLine(line);
}

Dynamic Objects and Dynamic Language Integration

Dynamic objects also provide integration capabilities with dynamic languages like IronPython:

using Microsoft.Scripting.Hosting;
using IronPython.Hosting;

// Create IronPython runtime
ScriptRuntime py = Python.CreateRuntime();
dynamic random = py.UseFile("random.py");

// Using methods from IronPython library
int[] items = Enumerable.Range(1, 7).ToArray();
for (int i = 0; i < 5; i++)
{
    random.shuffle(items);
    foreach (int item in items)
    {
        Console.WriteLine(item);
    }
    Console.WriteLine("-------------------");
}

Performance Considerations and Best Practices

While dynamic objects provide great flexibility, they should be used cautiously in performance-sensitive scenarios:

Conclusion

Dynamic objects in C# provide developers with powerful runtime flexibility. ExpandoObject is suitable for simple dynamic property addition scenarios, while inheriting from DynamicObject enables implementation of more complex custom dynamic behaviors. In practical development, appropriate dynamic object implementation methods should be chosen based on specific requirements, balancing flexibility with performance considerations.

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.