In C#, the dynamic keyword, the ExpandoObject class, and the DynamicObject class, are the main approaches to working with dynamic objects. However, ExpandoObject and DynamicObject are often used interchangeably, so it can be quite difficult to distinguish them. In this article, we will delve into the differences between ExpandoObject, DynamicObject, and dynamic. We will better understand these concepts and how we can use them effectively in our code.
Let’s start.
What Is Dynamic in C#?
Dynamic is a C# static type. It was introduced in C# to provide interoperability when working with dynamically typed languages like Python, working with COM Objects, when accessing the HTML DOM, or when working with JSON. When working with objects of the dynamic
type, we can try to access any member of the type even if it doesn’t exist and the compiler won’t show any error until we run the code.
Let’s create a person
object with one member:
dynamic person = new { Name = "Test" };
After that, let’s declare a new age
variable and assign the value of Age
from the person
object to it:
var age = person.Age;
Up to this point, the code doesn’t throw any exceptions but when we run our application, we get a RuntimeBinderException
. This is because the person object doesn’t have a member called Age
.
Also, we can assign a value of any type to a dynamic variable:
dynamic count = 1; Assert.IsType<int>(count);
In this method, we declare count
as a dynamic
variable, and assign to it the value of 1. Then, at runtime, the type of count
will be an int
. This means that dynamic objects will take the type of value they are assigned to.
Dynamic variables allow flexibility in our applications since variables are not checked by the compiler. However, using dynamic variables may lead to errors in our applications. If we misspell a member or try accessing a non-existent member, for instance, the application throws a RuntimeBinderException
since there’s no static checking at compile time.
What Is an ExpandoObject?
The ExpandoObject
class in C# allows us to instantiate objects that we can use to dynamically add or remove members at runtime. In addition to adding members, ExpandoObject
also enables us to set and get the values of the object’s members.
To create an instance of ExpandoObject
, we use the dynamic
keyword. Since we need to dynamically add members to ExpandoObject, we need to be able to call members that don’t exist in it without having a compilation error, and that is where the dynamic
keyword is useful.
Let’s create an instance of ExpandoObject:
dynamic book = new ExpandoObject(); book.Author = "John Doe"; book.Year = 2023; Assert.Equal("John Doe", book.Author); Assert.Equal(2023, book.Year);
Using the .
operator, we add two new properties with values to the book
object. Then, we just use the .
operator again to access those values.
Let’s also see how we can add methods to an ExpandoObject:
book.copiesSold = 1000; book.Sell = (Action)(() => { book.copiesSold++; }); book.Sell();
We first add copiesSold
member with a value of 1000
. Then, we add the Sell()
method as a delegate which increments the value of copiesSold
.
Calling this method, the value of copiesSold
increases by one every time.
Enumerating ExpandoObject Members
The ExpandoObject
class implements the IDictionary<string, object>
interface. This means that every time we add new members to an object, they are stored as key-value pairs.
To get all the values of the ExpandoObject
instance, we loop over the object, enumerating the values:
dynamic country = new ExpandoObject(); country.Continent = "Asia"; country.Population = "3 Billion people"; foreach (KeyValuePair<string, object> keyValuePair in country) { _testOutputHelper.WriteLine($"{keyValuePair.Key} : {keyValuePair.Value}"); }
We add a foreach
loop, in which we create an instance of the KeyValuePair
structure that we use to get the values of the dynamic object. We then print the results to the console. This comes in handy in situations where we don’t know the format of incoming data beforehand.
When we run this method, we get:
Continent : Asia Population : 3 Billion people
Now that we’ve learned how to add and read values from an ExpandoObject, let’s see how we can remove properties from the object.
Removing Properties From an ExpandoObject
Let’s create a new dynamic object and remove a property from it:
dynamic person = new ExpandoObject(); person.Age = 30; person.Name = "Jane Doe"; ((IDictionary<string, object>)person).Remove("Age");
We first create a person
object and add two properties to it. After that, we cast the person
object to the IDictionary<string, object>
interface. Then, we call the Remove()
method passing the Age
property. This deletes the property from the person
object.
ExpandoObject Property Changes
The ExpandoObject class also implements the INotifyPropertyChanged
interface, which fires a PropertyChanged
event every time we add, remove or update the object properties. The event notifies subscribers of the changes in the properties of the object.
Let’s examine this:
dynamic person = new ExpandoObject(); ((INotifyPropertyChanged)person).PropertyChanged += (_, e) => { _testOutputHelper.WriteLine($"Property changed: {e.PropertyName}"); }; person.Name = "John Doe";
We first declare a new person
dynamic object. Then, we subscribe to changes in the object by raising a PropertyChanged
event. After that, we add a new Name
property to the person
object.
Calling this method, we get:
Property changed: Name
What Is a DynamicObject?
The DynamicObject
class lets us create custom dynamic objects. It helps us specify the operations we can perform on dynamic objects and how we perform the operations.
However, we can only inherit the class in our applications since we cannot instantiate it directly. After inheriting the class, we override the methods we need to implement custom logic.
DynamicObject Methods
The main methods we’ll look at in this article are:
- TryGetMember() method
- TrySetMember() method
The TryGetMember()
method allows us to customize the behavior of accessing member values dynamically at runtime. To customize the behavior, we override the method:
public override bool TryGetMember(GetMemberBinder binder, out object result)
The GetMemberBinder
parameter provides information about the object whose member is being accessed. The result
parameter is the result of the get operation.
The TrySetMember()
method allows us to customize operations for setting member values of dynamic objects:
public override bool TrySetMember(SetMemberBinder binder, object value)
Similar to TryGetMethod
this method also takes two parameters. The SetMemberBinder
parameter provides information about the member being set. The value
parameter is the value assigned to the accessed member.
Both methods should return true
if the operation is successful, otherwise, they should return false
.
Working With the DynamicObject Type
Let’s create a new Person class that inherits DynamicObject
:
public class Person : DynamicObject { private readonly Dictionary<string, object?> _personalInformation; public Person() { _personalInformation = new Dictionary<string, object?>(); } }
The class has one field of type Dictionary<string, object?>
and a constructor.
Let’s now implement two methods of the DynamicObject
class:
public class Person : DynamicObject { private readonly Dictionary<string, object?> _personalInformation; public Person() { _personalInformation = new Dictionary<string, object?>(); } public override bool TryGetMember(GetMemberBinder binder, out object? result) { var key = binder.Name; return _personalInformation.TryGetValue(key, out result); } public override bool TrySetMember(SetMemberBinder binder, object? value) { var key = binder.Name; _personalInformation[key] = value; return true; } }
First, we override the TryGetMember()
method to get the value of a property from the _personalIformation
dictionary. Then, the TrySetMember()
method helps us set the value of a property on the _personalInformation
dictionary.
To test these methods, we can instantiate the Person
class as a dynamic object:
dynamic dynamicPerson = new Person(); person.Name = "Test"; person.Age = 30; person.Address = "1234 Good Street";
In this case, we’re adding three new properties to the person
dynamic object.
Let’s add a new method to the Person
class:
public void PrintInfo() { Console.WriteLine("Personal Information:"); foreach (var info in _personalInformation) { Console.WriteLine($"{info.Key}: {info.Value}"); } }
Calling this method, we get:
Personal Information: Name: Test Age: 30 Address: 1234 Good Street
The PrintInfo()
method dynamically reads values from the person
object and prints them to the console.
This is a very simplified use case of the DynamicObject
class. The method can however be inherited and customized further. The perfect use case would be reading values from a configuration file.
Having looked at the dynamic types individually, let’s proceed to look at the differences between them.
Differences Between ExpandoObject, DynamicObject, and Dynamic
The ExpandoObject class allows us to add members to a dynamic object instance at runtime and use them dynamically. Internally, it implements the IDictionary<string, object>
interface, which enables it to store properties and values in key-value pairs.
To add or access properties from the ExpandoObject
we can either use the .
operator or treat it as a dictionary. Also, we don’t need to explicitly define another class or override members in order to use it in our applications.
The DynamicObject is a more advanced class that allows us to customize the behavior of dynamic objects. Compared to the ExpandoObject
class, the DynamicObject
class is more flexible and more powerful because we can use it to create custom logic for operations on dynamic objects. It is a good choice for scenarios where we need to have more control over the dynamic objects we create.
dynamic
, on the other hand, is a type that helps us create dynamically-typed objects. When we declare a variable as dynamic, the compiler does not check the type of the variable. With this in mind, we can assign a value of any type to the dynamic object. We can also perform operations on the objects without compile-time checks. It is also the type that should be used in order to work with ExpandoObject
and DynamicObject
.
Conclusion
In this article, we have learned about the differences between ExpandoObject, DynamicObject, and dynamic. We have looked at their features and how we can make use of them in our applications. Knowing the appropriate use case for each of them can be quite difficult, especially for beginners. However, we hope this article has provided more insight.