# Dynamic Types

Course: [C# Advanced](https://www.devtutorials.ai/learn/advanced-csharp) · Section: Reflection and Metadata
Source: https://www.devtutorials.ai/learn/advanced-csharp/reflection-and-attributes/dynamic-types

> 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:

1. What the `dynamic` keyword does.
2. How types are checked when the program runs.
3. How to read and use members when type is unknown.
4. How to build objects on the fly with `ExpandoObject`.
5. How to make your own dynamic objects with `DynamicObject`.
6. How to work with data of unknown type.
7. When to use reflection and when to use `dynamic`.
8. What `dynamic` costs 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**.

```csharp runnable title="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 |

```csharp runnable title="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:

1. It looks at the **actual type** of the value right now.
2. It asks the **runtime binder** to find a member that matches, using C# rules.
3. 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.

```csharp runnable title="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.

```csharp runnable title="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.

```csharp runnable title="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.

```csharp runnable title="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.

```csharp runnable title="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.

```csharp runnable title="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.

```csharp runnable title="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.

```csharp runnable title="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 `ExpandoObject`s, and then you can use simple dots.

```csharp runnable title="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 `record` and use `JsonSerializer.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?** `dynamic` is 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:

```text
// 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.
- **`dynamic`** answers **"just call it."** You must know the member name when you write the code.

```csharp runnable title="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.

```csharp runnable title="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 `dynamic` code that uses it.
- **Anything touching `dynamic` becomes `dynamic`.** If you write `var total = order.Price * 2;`, then `total` is also `dynamic`. The problem spreads.

Stop the spread by saving the result to a real type right away:

```text
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

- `dynamic` turns off build-time checks. Members are found when the line runs.
- `var` is fully typed. `object` hides the type. `dynamic` decides at runtime.
- A missing member throws `RuntimeBinderException`.
- With `dynamic`, the actual type picks the method overload.
- `ExpandoObject` lets you add properties at runtime and is also an `IDictionary<string, object?>`.
- Inherit from `DynamicObject` to 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. `dynamic` calls members you already know by name.
- `dynamic` costs speed on first call and safety always. Keep it at the edges.

Next: Expression Trees
