TL;DR
  • dynamic lets you skip compile-time type checks member access is resolved at runtime.
  • Useful when:
    • You’re working with data that changes shape (e.g., JSON, user input, config files)
    • You need to support plugin-like behavior or custom logic injection
    • You want to write flexible, generic code that adapts to different objects
  • Downside: No IntelliSense, and runtime errors if the expected method or property doesn’t exist.
  • If your program logic depends on runtime decisions, dynamic gives you the flexibility to make it work.

Standard dynamic binding

“You don’t need to check stuff at compile time — I got this!”
That’s pretty much the philosophy of dynamic binding.

If you’re into reflection, scripting, or interop… dynamic binding is just for you!

dynamic fish = "test";

fish.ToUpper();        // Works
fish.ToSuperUpper();   // RuntimeBinderException (unless you've added an extension method...)

The .ToUpper() method isn’t part of the dynamic type itself, but since we know the runtime type is string, it works just fine.

However, since string doesn’t have a method called .ToSuperUpper() (assuming you didn’t add it as an extension), this line will cause a RuntimeBinderException and that sucks!


Static binding

This one you know! Static binding, compile-time safety!:

HelloWorlder hwer = new HelloWorlder();

hwer.helloWorld();

The type above does indeed contain a helloWorld() function (I made it myself, so I would know 😎).

But how does the compiler actually bind helloWorld() to HelloWorld?

  1. First, the compiler checks the HelloWorlder type for a method called helloWorld() with no parameters.

  2. If 1 fails, it checks for overloads with optional parameters.

  3. Still nothing? It looks into the base class (if any).

  4. Finally, it searches for extension methods where the first argument is HelloWorlder.

If no match is found you get the dreaded compile-time error, which is probably a good thing.


Object binding

Object hwer = new HelloWorlder();

hwer.helloWorld();

The code above fails miserably at complie time. And that is simply because the type Object knows nothing about any function called helloWorld (Look at the section above on how the compiler looks for stuff in types! Which is what we in latin call binding).


Lets change the type to dynamic!

dynamic hwer = new HelloWorlder();

hwer.helloWorld();

Phew now it compiles! Even though it is like Object a non-descriptive type, the dynamic tells the compiler to not bind this at compile time! So the compiler skips the scan i described above and simply pack it in a way so the binding can happen runtime. Pretty cool huh?


But why?!

If you wanna roll with for instance deserialized types from a string of json thats why!

string json = @"{ ""Name"": ""Rasmus"", ""Age"": 20 }";

dynamic result = JsonSerializer.Deserialize<ExpandoObject>(json);

Console.WriteLine(result.Name); // Alice
Console.WriteLine(result.Age);  // 34...  I mean 20.
!ExpandoObject!

An ExpandoObject is a special dynamic type in C# that lets you add or remove properties at runtime just like JavaScript objects.

It supports dot notation (obj.SomeProperty) and is used when thestructure isn’t known at compile time.

Or if you wanna return power to the people or in this case your consumers giving them powers to execute scripts inside your endpoints. Only do this in a sandboxed manner your CISO will not approve in any other case.

Lets say we have an endpoint consuming this string and assigning it to a variable:

string userScript = @"
    return data.Price < 100 && data.Volume > 10000;
";

Now lets say that the user also, had a bunch of params added to the request making us fetch ticker data for the apple stock and assigning that to a variable:

var data = new MarketData
{
    Symbol = "AAPL",
    Price = 95,
    Volume = 12000
};

Luckily we have this nuget package installed Microsoft.CodeAnalysis.CSharp.Scripting. And we know what this package does. (It executes scripts.).

dynamic result = await CSharpScript.EvaluateAsync<dynamic>(
    code: userScript,
    options: ScriptOptions.Default.AddReferences(typeof(MarketData).Assembly),
    globals: new Globals { data = data }
);

Console.log(result); // True!

You see what we did here? We gave the use access to executing logic on our server, which can be handy if you wanna enable user driven logic.

Dynamics is completely essential for this to work since we are not interested in what the user actually returns. If we didnt use dynamic we would have to guess what the script returns resulting in CompilationErrorException if we dont guess right.

Thank you for reading this blog post wait for me to dig deeper into the binding process dynamic conversions how to make the compiler treat the dynamic differently.

Profile
Rasmus Lagoni
Exploring language internals and real-world systems. Focused on C# and .NET.