Skip to content

JavaScript Envy in C# - Dynamic Properties

Published: at 03:22 PM

Source Generators

This is the first post in a series about things that you can do in JavaScript that are not so easy in C#… but just because they’re not easy, doesn’t mean they’re impossible!

Table of contents

Open Table of contents

Dynamic vs Static Types

In JavaScript, objects are flexible. They don’t have any set type, so they can contain whatever properties and methods you want and be molded, shaped, and extended with new properties at runtime, without any ceremony. This dynamism is something that C# developers might look at with a hint of envy.

The JavaScript Way

Imagine you have an object, and you want to add a new property to it. In JavaScript, it’s as simple as:

const obj = {};
obj["foo"] = "bar";

Or you can do the same using dot notation:

const obj = {};
obj.foo = "bar";

Either way, obj ends up with a property named foo containing the value bar. No class definitions, no hassle.

The C# Approach

C# is a statically typed language. Properties are predefined, and objects are instances of classes.

So how do we achieve the above in C#?

Dictionaries

One way is to use a specific class that let’s you do this, like Dictionary<TKey, TValue>:

var obj = new Dictionary<string, object?>();
obj["foo"] = "bar";

That works… and it probably mimics what’s going on in JavaScript most closely. Objects in JavaScript are essentially just dictionaries, with some dot notation added as syntactic sugar. So we could use dictionaries everywhere in C# and hack away just like front end devs. The only thing we’re really missing here is the nice dot notation.

And I guess that’s fine if you want to throw static typing out the window entirely and if you control the whole codebase. Sometimes we want to make use of classes that other developers have written though… possibly sealed classes in third party libraries - and not all of these will inherit from Dictionary<TKey, TValue> 😟

External Dictionary

To augment an instance of a class that we didn’t author and we can’t modify, one naive approach is to use Dictionary to map from that object to somewhere else where we hold the properties. So something like this (not thread safe and I wouldn’t recommend using it… but for demonstration purposes):

static class ObjectExtensions
{
    private static readonly DynamicProperties Map = new DynamicProperties();

    public static T? GetProperty<T>(this object source, string key) => (T?)Map[source][key];
    public static void SetProperty(this object source, string key, object? value) => Map[source][key] = value;

    class DynamicProperties
    {
        private readonly Dictionary<object, Dictionary<string, object?>> _properties = new();

        public Dictionary<string, object?> this[object source]
        {
            get
            {
                if (_properties.TryGetValue(source, out var dict)) return _properties[source];
                dict = new Dictionary<string, object?>();
                _properties.Add(source, dict);
                return _properties[source];
            }
        }
    }
}

Having defined that somewhere in our application, we could now do this:

var myClass = new object();
myClass.SetProperty("foo", "bar");

There is a fundamental problem with this code however. ObjectExtensions.Map will store a reference to any object that we add dynamic properties to and will prevent it from being garbage collected! 🙀

ConditionalWeakTable to the Rescue

Found in the System.Runtime.CompilerServices namespace, the ConditionalWeakTable:

Enables compilers to dynamically attach object fields to managed objects.

ConditionalWeakTable<TKey,TValue> is a bit like Dictionary<TKey, TValue> however it only holds weak references to all it’s keys… which means it doesn’t prevent any objects that are used as keys from being garbage collected (the references it holds aren’t considered by the garbage collector)… and when any objects that serve as key values pass out of scope, ConditionalWeakTable auto-magically removes the associated values from it’s table! This is very cool and what makes ConditionalWeakTable so powerful. You can see why it’s useful for compilers… but we can use it too 😎

Armed with the ConditionalWeakTable then, we can modify our ObjectExtensions to look like this instead:

static class ObjectExtensions
{
    private static ConditionalWeakTable<object, Dictionary<string, object?>> Map { get; } = new();

    private static Dictionary<string, object?> AssociatedProperties(this object source) =>
        Map.GetValue(source, _ => new Dictionary<string, object?>());

    public static T? GetProperty<T>(this object source, string key) => (T?)source.AssociatedProperties()[key];
    public static void SetProperty(this object source, string key, object? value) => source.AssociatedProperties()[key] = value;
}

And now we can safely set and get arbitrary properties on any object, with no concern for static typing, so that our C# applications can crash at runtime just as often as their JavaScript cousins!

var myClass = new object();
myClass.SetProperty("foo", "bar");
Console.WriteLine($"Foo = {myClass.GetProperty<string>("foo")}");

We don’t get the fancy dot notation that you get with JavaScript but it’s still pretty cool and gives us equivalent functionality in terms of dynamically adding class members to arbitrary objects.

Source code and NuGet package

The above is a fairly minimal solution to the problem with no null checks or anything… You can get the source code for a more polished solution at https://github.com/mentaldesk/fuse or just add the MentalDesk.Fuse NuGet package to your solution so that you can start sprinkling dynamic properties all over the objects you use!

Words of caution

This post really should come with a warning label at the top (sorry about that - I put it at the bottom). Although you can add properties to objects in C# dynamically, you should probably do so with extreme caution and only when you can’t really find a better solution. Under the hood, we’re just using a fancy dictionary and there’s nothing to stop us doing this:

var myClass = new object();
myClass.SetProperty("foo", "bar");

… and then somewhere else in our code doing this:

myClass.SetProperty("foo", 42);

Which makes it problematic when we want to do this:

var wtf = myClass.GetProperty("foo");

Wrapping Up

ConditionalWeakTable allows C# developers to do all those dangerous things that JavaScript programmers have been doing for years.

However with great power comes great potential confusion… Use these sparingly (ideally not at all - but I have bumped into two real world use cases for this myself in the past year). Keep your code clean and your developers sane.

Stay tuned for more adventures in doing dangerous stuff with C#.

Happy coding and may your objects always be just dynamic enough!