Dynamic Types
By the Syncfusion DevTutorials team · Last updated
Resolve members at runtime with dynamic: runtime binding, dynamic member access and calls, ExpandoObject, DynamicObject, unknown data, reflection vs dynamic and performance.
Introduction to Dynamic Types
Think about two ways to send a parcel.
At a post office counter, the clerk checks everything before the parcel leaves: the address, the weight, the stamps. If something is wrong, you fix it right there.
With a drop box, no one checks anything. You drop the parcel and leave. If the address is wrong, you find out days later when it comes back.
Normal C# works like the counter. The compiler checks every method call before your program runs. dynamic works like the drop box. The compiler skips the checks. The method call is checked only when that line actually runs.
In simple words: dynamic tells the compiler, "I will handle this at runtime."
This helps when the compiler cannot know the type:
- data from JSON or a database that has no matching C# class,
- objects from COM libraries like Excel automation,
- code that calls Python or JavaScript.
Easy Way to Remember: a normal type is checked before you ship. dynamic is checked when it arrives.
What You Will Learn
In this lesson, you will learn:
- What the
dynamickeyword does. - How types are checked when the program runs.
- How to read and use members when type is unknown.
- How to build objects on the fly with
ExpandoObject. - How to make your own dynamic objects with
DynamicObject. - How to work with data of unknown type.
- When to use reflection and when to use
dynamic. - What
dynamiccosts in speed and safety.
All examples on this page run in your browser. The browser runtime includes the C# runtime binder that dynamic needs. Examples using COM, like Excel automation, only work on Windows with Office installed, so they are shown as plain code.
Start With a Working Example
Press Run.
One variable, three different types
using System;
dynamic value = 10;
Console.WriteLine($"{value} is {value.GetType().Name}, plus 5 = {value + 5}");
value = "Chennai";
Console.WriteLine($"{value} is {value.GetType().Name}, length = {value.Length}");
value = new DateTime(2025, 8, 15);
Console.WriteLine($"{value:dd MMM yyyy} is {value.GetType().Name}, year = {value.Year}");
The same variable holds an int, then a string, then a DateTime. Each time, value.Length or value.Year is looked up while your program runs, on whatever object is inside at that moment.
Try This
Add Console.WriteLine(value.Length); as the last line and run. The compiler accepts it. At runtime it fails, because a DateTime has no Length. You will see why in the next sections.
dynamic Keyword
When you declare a variable as dynamic, the compiler stops checking what you do with it.
Compare dynamic with var and object. They look similar, but work very differently:
var | object | dynamic | |
|---|---|---|---|
| Type decided | At build time, from the value | At build time: always object | When the program runs, from the real value |
Can call value.Length? | Yes, if that type has it | No, build error | Yes, checked when this line runs |
| Mistakes found | When you build | When you build | When the program runs |
| IntelliSense help? | Yes, full list | Only object members | No, none |
var, object and dynamic side by side
using System;
var a = "hello"; // the compiler knows a is a string
object b = "hello"; // the compiler only knows b is an object
dynamic c = "hello"; // the compiler does not check; it waits until runtime
Console.WriteLine(a.Length); // checked when you build
// Console.WriteLine(b.Length); // build error: object has no Length
Console.WriteLine(((string)b).Length); // you must cast it first
Console.WriteLine(c.Length); // checked when this line runs
// a = 5; // build error: a is a string forever
c = 5; // fine: c can hold any type
Console.WriteLine(c * 2);
In simple words: var is a shortcut for a real type. object is a type that hides details. dynamic turns off type checking.
dynamic has no IntelliSense and no build-time checks. A typo like value.Lenght builds without error and fails only when that line runs. Use dynamic in small, clearly marked places, not everywhere.
Runtime Type Resolution
When your code uses a dynamic value, the compiler writes a small call site. When that line runs, the call site does three things:
- It looks at the actual type of the value right now.
- It asks the runtime binder to find a member that matches, using C# rules.
- It remembers the answer, so the next call with the same type is faster.
The runtime binder is the part of .NET that does what the compiler normally does, but while your program runs. It is in Microsoft.CSharp.dll.
Because this lookup happens at runtime, overload resolution uses the actual type, not the declared type. This creates a useful trick.
The real type picks the overload
using System;
object[] items = { 42, "Asha", 3.5, true };
Console.WriteLine("Declared as object:");
foreach (object item in items)
Console.WriteLine($" {Describer.Describe(item)}");
Console.WriteLine("Cast to dynamic:");
foreach (object item in items)
Console.WriteLine($" {Describer.Describe((dynamic)item)}");
static class Describer
{
public static string Describe(object value) => "some object";
public static string Describe(int value) => $"an int: {value}";
public static string Describe(string value) => $"a string: {value}";
public static string Describe(double value) => $"a double: {value}";
}
With object, the compiler always picks Describe(object). With (dynamic)item, the runtime binder looks at the actual value and picks the best match. true has no bool match, so it falls back to Describe(object).
What Happens When There Is No Match
If the runtime binder cannot find a matching member, it throws RuntimeBinderException. This is like the drop box parcel coming back.
A missing member fails at runtime
using System;
using Microsoft.CSharp.RuntimeBinder;
dynamic city = "Mumbai";
try
{
Console.WriteLine(city.ToUpper()); // string has ToUpper: works
Console.WriteLine(city.Population); // string has no Population
}
catch (RuntimeBinderException ex)
{
Console.WriteLine($"Runtime error: {ex.Message}");
}
Dynamic Member Access
With dynamic, you read properties and fields with a simple dot. No GetProperty, no GetValue.
This works well with anonymous types, which have no name you can write. Anonymous types are internal, so this works when the code that reads them is in the same project.
Read anonymous objects without knowing their type
using System;
using System.Collections.Generic;
var rows = new List<dynamic>
{
new { Name = "Asha", City = "Pune", Orders = 12 },
new { Name = "Ravi", City = "Chennai", Orders = 4 },
new { Name = "Meera", City = "Delhi", Orders = 9 },
};
foreach (dynamic row in rows)
{
string level = row.Orders >= 9 ? "Gold" : "Silver";
Console.WriteLine($"{row.Name,-6} {row.City,-8} {level}");
}
Every row.Orders and row.Name is looked up while the program runs. If you change the anonymous type later, nothing reminds you to update this code. That is the trade-off.
Dynamic Method Invocation
Calling methods works the same way: value.MethodName(arguments).
The runtime binder finds a method on the actual type with that name and matching arguments. This is called duck typing: "if it walks like a duck and quacks like a duck, treat it as a duck." The objects do not need to share an interface. They just need a method with the right name.
Duck typing with dynamic
using System;
dynamic[] devices = { new Printer(), new Speaker(), new Lamp() };
foreach (dynamic device in devices)
{
try
{
device.TurnOn();
Console.WriteLine($" status: {device.Status()}");
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)
{
Console.WriteLine($" {device.GetType().Name} cannot report a status");
}
}
class Printer
{
public void TurnOn() => Console.WriteLine("Printer warming up");
public string Status() => "ready, 120 sheets";
}
class Speaker
{
public void TurnOn() => Console.WriteLine("Speaker on");
public string Status() => "volume 40%";
}
class Lamp
{
public void TurnOn() => Console.WriteLine("Lamp on");
}
Printer, Speaker and Lamp share no interface, but one loop calls them all. Lamp has no Status method, so that one call fails when the line runs.
In your own code, an interface is almost always better than duck typing. The compiler checks it, the editor completes it, and missing methods are caught before the program runs. Use dynamic calls for types you cannot change.
ExpandoObject
An ExpandoObject is an object you can add properties to while your program runs. It starts empty. When you set obj.Anything = value, the property Anything is created.
It lives in System.Dynamic, and you must use it with a dynamic variable.
Add properties on the fly
using System;
using System.Dynamic;
dynamic product = new ExpandoObject();
product.Name = "Wireless Mouse";
product.Price = 799m;
product.Tags = new[] { "computer", "sale" };
Console.WriteLine($"{product.Name}: Rs. {product.Price}");
Console.WriteLine($"Tags: {string.Join(", ", product.Tags)}");
// You can even add a method, as a delegate
product.PriceWithTax = (Func<decimal>)(() => product.Price * 1.18m);
Console.WriteLine($"With tax: Rs. {product.PriceWithTax():0.00}");
ExpandoObject Is Also a Dictionary
Inside, an ExpandoObject is a dictionary of property names and values. It implements IDictionary<string, object?>, so you can:
- add a property whose name you only have as text,
- loop over every property,
- check if a property exists before you read it.
Use an ExpandoObject as a dictionary
using System;
using System.Collections.Generic;
using System.Dynamic;
dynamic person = new ExpandoObject();
person.Name = "Priya";
var fields = (IDictionary<string, object?>)person;
fields["City"] = "Hyderabad"; // name chosen at runtime
fields["Joined"] = 2021;
Console.WriteLine($"Has City? {fields.ContainsKey("City")}");
Console.WriteLine($"Has Phone? {fields.ContainsKey("Phone")}");
Console.WriteLine($"City via dot: {person.City}");
foreach (var (key, value) in fields)
Console.WriteLine($" {key} = {value}");
fields.Remove("Joined");
Console.WriteLine($"Properties left: {fields.Count}");
If you read a property that does not exist, it throws RuntimeBinderException. Use ContainsKey first when you are not sure.
Dynamic Objects
ExpandoObject just stores values. Sometimes you want an object that decides what to do when someone reads a property it does not have.
For that, inherit from DynamicObject and override the methods you need:
| Override | Runs when someone... |
|---|---|
TryGetMember | reads obj.Something |
TrySetMember | writes obj.Something = value |
TryInvokeMember | calls obj.Something(args) |
Each method returns true if it handled the request, or false to make the runtime throw.
A settings object that reads from a dictionary
using System;
using System.Collections.Generic;
using System.Dynamic;
var raw = new Dictionary<string, string>
{
["theme"] = "dark",
["fontsize"] = "16",
};
dynamic settings = new Settings(raw);
Console.WriteLine(settings.Theme); // case does not matter here
Console.WriteLine(settings.FontSize);
Console.WriteLine(settings.Language); // missing: returns a default
settings.Language = "ta-IN";
Console.WriteLine(settings.Language);
class Settings : DynamicObject
{
private readonly Dictionary<string, string> _values;
public Settings(Dictionary<string, string> values) =>
_values = new Dictionary<string, string>(values, StringComparer.OrdinalIgnoreCase);
public override bool TryGetMember(GetMemberBinder binder, out object? result)
{
result = _values.TryGetValue(binder.Name, out var text) ? text : "(not set)";
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object? value)
{
_values[binder.Name] = value?.ToString() ?? "";
return true;
}
}
binder.Name is the property name the caller wrote. The object turns every settings.Something into a dictionary lookup, ignores upper and lower case, and never throws when a key is missing.
Handling Method Calls
TryInvokeMember handles method calls. This example builds tiny "query" methods from just the method name.
Methods that do not exist until you call them
using System;
using System.Dynamic;
using System.Linq;
var people = new[]
{
new Person("Asha", "Pune"),
new Person("Ravi", "Chennai"),
new Person("Kiran", "Pune"),
};
dynamic finder = new Finder(people);
// Save the dynamic result to a real type before using it.
Person[] inPune = finder.FindByCity("Pune");
Person[] named = finder.FindByName("Ravi");
Console.WriteLine(string.Join(", ", inPune));
Console.WriteLine(string.Join(", ", named));
record Person(string Name, string City)
{
public override string ToString() => $"{Name} ({City})";
}
class Finder : DynamicObject
{
private readonly Person[] _people;
public Finder(Person[] people) => _people = people;
public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args, out object? result)
{
result = null;
if (!binder.Name.StartsWith("FindBy") || args is not { Length: 1 }) return false;
string field = binder.Name.Substring("FindBy".Length); // "City" or "Name"
var property = typeof(Person).GetProperty(field);
if (property == null) return false;
result = _people.Where(p => Equals(property.GetValue(p), args[0])).ToArray();
return true;
}
}
FindByCity and FindByName are not written anywhere. The object reads the method name, then uses reflection to find the matching property. Old ORMs used this exact trick.
Working With Unknown Types
The most common real reason for dynamic is data whose shape you learn only when the program runs, like JSON from an API.
System.Text.Json does not give you dynamic objects directly. A small helper can turn JSON into ExpandoObjects, and then you can use simple dots.
Turn JSON into dynamic objects
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text.Json;
string json = """
{
"orderId": 5012,
"customer": { "name": "Meera", "city": "Kochi" },
"items": [
{ "title": "Notebook", "qty": 3 },
{ "title": "Pen", "qty": 10 }
]
}
""";
dynamic order = ToDynamic(JsonDocument.Parse(json).RootElement);
Console.WriteLine($"Order {order.orderId} for {order.customer.name} in {order.customer.city}");
foreach (dynamic item in order.items)
Console.WriteLine($" {item.qty} x {item.title}");
static object? ToDynamic(JsonElement e) => e.ValueKind switch
{
JsonValueKind.Object => e.EnumerateObject().Aggregate(
(IDictionary<string, object?>)new ExpandoObject(),
(obj, p) => { obj[p.Name] = ToDynamic(p.Value); return obj; }),
JsonValueKind.Array => e.EnumerateArray().Select(ToDynamic).ToList(),
JsonValueKind.Number => e.TryGetInt64(out long n) ? n : e.GetDouble(),
JsonValueKind.String => e.GetString(),
JsonValueKind.True => true,
JsonValueKind.False => false,
_ => null,
};
Safer Choices for JSON
dynamic is quick to write, but safer choices are usually better:
- Know the shape? Write a
recordand useJsonSerializer.Deserialize<Order>(json). You get full type checking. - Unknown shape? Use
JsonNode:node["customer"]?["name"]?.GetValue<string>(). It is clear about missing values. - Quick script or prototype?
dynamicis fine.
COM Interop
Before C# 4, automating Excel or Word from C# meant long casts on every line. dynamic made it short. This only runs on Windows with Office, so here it is as plain code:
// Windows only, with Microsoft Excel installed.
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application")!)!;
excel.Visible = true;
dynamic book = excel.Workbooks.Add();
book.ActiveSheet.Cells[1, 1].Value = "Sales";
book.ActiveSheet.Cells[1, 2].Value = 125000;
Reflection vs Dynamic
Reflection and dynamic both work with types the compiler does not know. They solve different problems.
- Reflection answers "what is there?" It lists members, reads attributes and finds private members.
dynamicanswers "just call it." You must know the member name when you write the code.
The same job two ways
using System;
using System.Reflection;
object box = new Parcel { Weight = 2.5, City = "Jaipur" };
// Reflection: clear, can discover members, more code
PropertyInfo weightInfo = box.GetType().GetProperty("Weight")!;
double w1 = (double)weightInfo.GetValue(box)!;
Console.WriteLine($"Reflection: {w1} kg");
// dynamic: short, but you must know the name when writing the code
dynamic d = box;
double w2 = d.Weight;
Console.WriteLine($"dynamic: {w2} kg");
// Only reflection can answer "what properties exist?"
foreach (var p in box.GetType().GetProperties())
Console.WriteLine($" found property: {p.Name}");
class Parcel
{
public double Weight { get; set; }
public string City { get; set; } = "";
}
| Question | Reflection | dynamic |
|---|---|---|
| List all members of a type? | Yes | No |
| Read attributes? | Yes | No |
| Use a member name stored as text? | Yes, GetProperty(name) | No, the name is in the code |
| Access private members? | Yes, with BindingFlags | No, uses normal C# rules |
| Short and easy to read? | No | Yes |
| Choose the overload by actual type? | You do it manually | Automatic |
Works with DynamicObject and COM? | No | Yes |
Easy Way to Remember: reflection is a map for exploring. dynamic is a shortcut for when you know where you are going.
Dynamic Type Performance Considerations
dynamic has two costs: speed and safety.
Speed
The first time a dynamic call runs, the runtime binder does the work the compiler normally does. That first call is slow. The runtime then remembers the answer for that type, so later calls are much faster, but still slower than a normal call.
Typed, dynamic and reflection calls compared
using System;
using System.Diagnostics;
var item = new Item { Price = 10 };
dynamic dyn = item;
var info = typeof(Item).GetProperty("Price")!;
const int Rounds = 200_000;
long sum = 0;
var watch = Stopwatch.StartNew();
for (int i = 0; i < Rounds; i++) sum += item.Price;
Console.WriteLine($"Typed: {watch.ElapsedMilliseconds} ms");
watch.Restart();
for (int i = 0; i < Rounds; i++) sum += dyn.Price;
Console.WriteLine($"dynamic: {watch.ElapsedMilliseconds} ms");
watch.Restart();
for (int i = 0; i < Rounds; i++) sum += (int)info.GetValue(item)!;
Console.WriteLine($"Reflection: {watch.ElapsedMilliseconds} ms");
class Item { public int Price { get; set; } }
Your numbers will change between runs. Normal typed code is always fastest. A dynamic call site that has been called before is usually as fast as cached reflection.
Safety
The bigger cost is safety:
- Typos are found only when the line runs, possibly after you ship to production.
- Renaming a property in Visual Studio does not update
dynamiccode that uses it. - Anything touching
dynamicbecomesdynamic. If you writevar total = order.Price * 2;, thentotalis alsodynamic. The problem spreads.
Stop the spread by saving the result to a real type right away:
decimal total = order.Price * 2; // now total is typed and checked from here on
A good rule: keep dynamic at the edge of your program, where new data comes in. Convert it to real types immediately, and keep the rest of your code fully typed.
Where Dynamic Types Are Used
- COM automation: Excel, Word and Outlook from C#.
- Interop with dynamic languages: IronPython and JavaScript engines.
- Quick scripts: reading JSON or CSV with a shape that changes.
- Older ASP.NET MVC:
ViewBag.Title = "Home";is a dynamic object. - Testing tools that call private or generated members by name.
Common mistakes
| Mistake | Symptom | Fix |
|---|---|---|
| Typo in a member name | RuntimeBinderException at runtime | Use a real type when you know it |
Extension method on dynamic | RuntimeBinderException | Cast first, or call the static method directly |
Reading a missing ExpandoObject property | RuntimeBinderException | Check with ContainsKey first |
var result of a dynamic expression | dynamic spreads everywhere | Assign to a real type instead |
dynamic inside a hot loop | Slow code | Use typed code or a cached delegate |
What to Remember
dynamicturns off build-time checks. Members are found when the line runs.varis fully typed.objecthides the type.dynamicdecides at runtime.- A missing member throws
RuntimeBinderException. - With
dynamic, the actual type picks the method overload. ExpandoObjectlets you add properties at runtime and is also anIDictionary<string, object?>.- Inherit from
DynamicObjectto decide what happens when someone reads, writes or calls. - The runtime binder cannot see extension methods. Cast to a real type first.
- Reflection explores types.
dynamiccalls members you already know by name. dynamiccosts speed on first call and safety always. Keep it at the edges.
Next: Expression Trees