This article will present code that shows how we can create a custom dynamic object and control its behavior, supporting thread safety when setting and getting members and allowing anonymous type initialization.
The code is available in this GitHub repo :
git clone
https://github.com/toreaurstadboss/DynamicObjectThreadSafe.git
The custom dynamic object inherits from the class
DynamicObject and uses a thread safe dictionary.
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace DynamicObjectThreadSafe
{
public class ThreadSafeDynamicObject : DynamicObject, IEnumerable<KeyValuePair<string, object>>
{
public ThreadSafeDynamicObject()
{
}
public ThreadSafeDynamicObject(dynamic members)
{
dynamic membersDict = ToDictionary(members);
InitMembers(membersDict);
}
private IDictionary<string, object> ToDictionary(object data)
{
var attr = BindingFlags.Public | BindingFlags.Instance;
var dict = new Dictionary<string, object>();
foreach (var property in data.GetType().GetProperties(attr))
{
if (property.CanRead)
{
dict.Add(property.Name, property.GetValue(data, null));
}
}
return dict;
}
private void InitMembers(IDictionary<string, object> membersDict)
{
foreach (KeyValuePair<string, object> member in membersDict)
{
_members.AddOrUpdate(member.Key, member.Value, (key, oldValue) => member.Value);
}
}
private readonly ConcurrentDictionary<string, object> _members = new ConcurrentDictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return _members.TryGetValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_members.AddOrUpdate(binder.Name, value, (key, oldvalue) => value);
return true;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return _members.Keys.ToList().AsReadOnly();
}
public override string ToString()
{
return JsonSerializer.Serialize(_members);
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return _members.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _members.GetEnumerator();
}
}
}
The method
ToDictionary transforms an input object, for example an anonymous class object via boxing (by accepting an object as parameter and implicitly 'boxing' it as a object) and the InitMember method will then populate the
ConcurrentDictionary<string,object> object. This will allow us to pass anonymous objects and cast the object into a dynamic object, for further consumption. For example outputting fields of it. Now why would you use dynamic objects
like this would you say? Dynamic objects are practical in many situations where you do not know the type until runtime. Many imlpementations of custom dynamic objects use Dictionary as a 'backing store' for the fields/properties/members of the object.
This implementation uses ConcurrentDictionary and there should be thread safe concerning retrieving or setting members as shown in the overrides of methods
TryGetMember and
TrySetMember.
The override for method GetDynamicMemberNames is for showing members in the debugger in the 'Dynamic view' to inspect the dynamic object properly. The GetEnumerator method overrides are to support casting the dynamic object to IDictionary<string,object>
The following tests is then passing :
[Fact]
public void It_Can_Accept_Anonymous_Type_initialization()
{
dynamic threadSafeToyota = new ThreadSafeDynamicObject(new
{
Make = "Toyota",
Model = "CR-H",
Propulsion = new
{
IsHybrid = true,
UsesPetrol = true,
ElectricMotor = true
}
});
Assert.Equal("Toyota", threadSafeToyota.Make);
Assert.Equal("CR-H", threadSafeToyota.Model);
Assert.Equal(true, threadSafeToyota.Propulsion.IsHybrid);
Assert.Equal(true, threadSafeToyota.Propulsion.UsesPetrol);
Assert.Equal(true, threadSafeToyota.Propulsion.ElectricMotor);
}
And since this object is dynamic, we can extend it and adjust its members as dynamic allows you and as you can see we both can instantiate the dynamic object via anonymous type instance or populating it manually one property at a time. And doing so in a thread-safe many, for better support in
multithreaded environments which are to expected many places today.
Share this article on LinkedIn.
The System.Text.Json nuget package is used inside the ToString() method to show the contents of the members of the dynamic object.
ReplyDelete