All of BiblioTech's code written so far is strongly typed: the compiler knows, on every line, the exact type of every variable (Book, string, List<Member>...) and rejects any operation that doesn't fit — accessing a nonexistent property, passing an argument of the wrong type — before the program ever runs. C# also offers a special type that deliberately gives up that checking: dynamic. This lesson explains what it is, how it differs from object and from var (with which it's frequently confused), when it makes sense to use it, and why BiblioTech's domain, deliberately typed since Module 1, should keep avoiding it in its business code.

Contents

  1. The dynamic type: resolution at runtime
  2. dynamic versus object: same data, checked at different times
  3. dynamic versus var: dynamic isn't the same as inferred
  4. ExpandoObject: dynamic objects with no defined class
  5. When it makes sense to use dynamic
  6. Why to avoid dynamic in BiblioTech's domain
  7. Example: reading a JSON of unknown structure with dynamic, versus the typed approach

  1. The dynamic type: resolution at runtime

A variable declared as dynamic tells the compiler: "don't check anything about this variable now; resolve it when the program runs." Any access to a property, a method, or an operator on a dynamic variable is deferred until runtime, at which point the .NET runtime (the Dynamic Language Runtime, DLR) tries to resolve it against the real type of the value it holds at that instant:

dynamic value = "Hopscotch";
Console.WriteLine(value.Length); // 10: at runtime, "Hopscotch" is a string, which has Length

value = 42;
Console.WriteLine(value.Length); // Runtime exception: int has no Length property

The first Console.WriteLine compiles and works because, at execution time, value holds a string, and string does have Length. The second compiles just as fine — the compiler checks nothing about dynamic — but fails at runtime, with a RuntimeBinderException, because int has no Length property at all. This is the central cost of dynamic: errors the compiler would catch immediately on any other type turn into exceptions that only surface when the program runs, possibly long after it was written.

  1. dynamic versus object: same data, checked at different times

object (the most general type in C#'s type system, Module 3) and dynamic might look similar — both can hold any value — but they differ in when the type gets checked before operating on it:

object asObject = "Hopscotch";
// asObject.Length;         // COMPILATION error: object has no Length
int length = ((string)asObject).Length; // an explicit cast is needed first

dynamic asDynamic = "Hopscotch";
int dynamicLength = asDynamic.Length;   // compiles with no cast; resolved at runtime
object dynamic
Type checking At compile time (requires an explicit cast to use type-specific members) Deferred to runtime
Accessing members of the real type Requires is/as/cast first (Module 3) Direct, with no cast, assuming it will exist
Error if the member doesn't exist At compile time (won't compile) At runtime (RuntimeBinderException)
IDE autocomplete Full, on the type after the cast None: the IDE can't know what members it will have
Performance Same as a normal call Slower: every operation is resolved dynamically each time

In practice, object forces you to be explicit about what type you actually expect (with is/as, as in Polymorphism, Module 3) before operating on the value; dynamic lets you skip that step, at the cost of losing any guarantee that the accessed member will really exist when the program runs.

  1. dynamic versus var: dynamic isn't the same as inferred

It's a very common beginner mistake to confuse dynamic with var (Module 1), because both let you omit the type name when declaring the variable. The difference is fundamental:

var title = "Hopscotch";     // the compiler INFERS that title is a string, and fixes it forever
// title = 42;                 // COMPILATION error: an int can't be assigned to a string variable

dynamic dynamicValue = "Hopscotch"; // the compiler does NOT fix any concrete type
dynamicValue = 42;                   // compiles perfectly: dynamic can change type at any moment

var is pure syntactic sugar: the compiler deduces the exact type from the initial value (here, string) and, from that point on, title behaves exactly as if you'd written string title = "Hopscotch"; — with all the usual type checks. dynamic, by contrast, is a real type in the type system that gives up those checks entirely: a dynamic variable can hold a string on one line and an int on the next, with the compiler never objecting.

var dynamic
What the compiler does Infers the real type once, at declaration Fixes no type at all; defers everything to runtime
Can the variable change type? No: the inferred type stays fixed Yes: it can hold values of different types at different times
Type checking Complete, at compile time (like any explicit type) None at compile time
Runtime cost None (it's just a shorter declaration) The cost of resolving every operation dynamically

  1. ExpandoObject: dynamic objects with no defined class

ExpandoObject (from System.Dynamic) takes the idea of dynamic one step further: it lets you create an object with no class defining it in advance, adding properties to it on the fly, at runtime:

using System.Dynamic;

dynamic informalBook = new ExpandoObject();
informalBook.Title = "Hopscotch";
informalBook.Author = "Julio Cortazar";
informalBook.PagesRead = 120; // no class ever declared this property

Console.WriteLine($"{informalBook.Title}, page {informalBook.PagesRead}");

No BiblioTech class defines PagesRead: ExpandoObject lets you add that property on the fly because, underneath, it's nothing more than a name-value dictionary disguised as an object with dot syntax. It's useful in scripting or rapid-prototyping scenarios, where defining a whole class for a data structure used only once would be disproportionate; for any data that's part of BiblioTech's stable domain, a normal class (like Book) remains a far better choice, for the reasons covered in section 6.

  1. When it makes sense to use dynamic

dynamic isn't a design flaw in C#: there are real scenarios where giving up compile-time type checking is exactly what's needed:

Scenario Why dynamic fits
COM interoperability (Office, Excel/Word automation from .NET) COM APIs don't expose static types the C# compiler can verify in advance
Untyped JSON of variable or unknown structure When it's not worth it (or not possible) to define a class for every possible shape of a response
Scripting scenarios or interactive tools Fast prototyping where flexibility matters more than type safety
Typed domain code (BiblioTech: Book, Member, Loan...) Doesn't fit: see section 6

  1. Why to avoid dynamic in BiblioTech's domain

BiblioTech's entire model — LibraryItem, Book, Magazine, Member, Loan — has been built, module by module, relying precisely on what dynamic discards:

  • Compile-time type safety: book1.Isbn fails immediately while writing the code, with a clear message, if Isbn didn't exist or were misspelled; with dynamic book1, the same error would only show up as an exception when that exact line ran, possibly many runs after it was written.
  • IDE-assisted autocomplete and refactoring: the editor always knows Book's exact members thanks to static typing; on a dynamic variable, the IDE can offer no help at all, because it doesn't know what type it will have until the program runs.
  • Performance: every operation on dynamic is resolved dynamically at runtime (through a resolution pipeline and an internal DLR cache), slower than the direct, already-resolved call on a static type like Book.

Replacing, say, Book book1 = ... with dynamic book1 = ... anywhere in BiblioTech wouldn't bring any advantage (the real type is always known in advance) and would bring a clear loss: errors the compiler catches today would become runtime exceptions, and the editor would stop helping with autocomplete. The general rule: use dynamic only when the type truly isn't known in advance and there's no reasonable way to model it with a class; in any other case, C#'s static typing is the right choice.

  1. Example: reading a JSON of unknown structure with dynamic, versus the typed approach

The JSON and REST APIs lesson (Module 5) defined ExternalBookMetadata and Publisher to deserialize, in typed fashion, the response of an external book-metadata service whose structure was known in advance. But not every external API documents its response that clearly; sometimes all you have is a sample JSON, of variable structure, with no class already written for it. dynamic, combined with the dynamic type returned by JsonSerializer.Deserialize<dynamic> over a JsonElement, lets you explore it without defining classes ahead of time:

using System.Text.Json;

string unknownJson =
    """
    {
      "isbn": "978-84-376-0495-4",
      "publisher": { "name": "Sudamericana", "country": "Argentina" },
      "genres": ["Fiction", "Latin American Literature"]
    }
    """;

// Approach with dynamic: no class defined in advance
JsonElement root = JsonSerializer.Deserialize<JsonElement>(unknownJson);
dynamic document = root;

Console.WriteLine(document.GetProperty("isbn").GetString());                        // "978-84-376-0495-4"
Console.WriteLine(document.GetProperty("publisher").GetProperty("country").GetString()); // "Argentina"
// Equivalent typed approach, as in the JSON and REST APIs lesson (Module 5)
ExternalBookMetadata? metadata =
    JsonSerializer.Deserialize<ExternalBookMetadata>(unknownJson,
        new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });

Console.WriteLine(metadata?.Isbn);              // "978-84-376-0495-4"
Console.WriteLine(metadata?.Publisher.Country); // "Argentina"

Both snippets read the same data, but with very different guarantees: document.GetProperty("isbn") doesn't check at compile time that the "isbn" key exists in the JSON — if it didn't exist, or were misspelled ("ivbn"), the error would only show up as an exception when that exact line ran — while metadata?.Isbn is a real property of ExternalBookMetadata, verified by the compiler, with IDE autocomplete, and with a safe, explicit null value if deserialization doesn't find the expected data. dynamic can be reasonable for a quick, one-off exploration of a JSON whose structure isn't yet known; once the structure settles down (as already happened with ExternalBookMetadata), migrating to typed classes is almost always the right call for code that's going to be maintained over time.

flowchart LR
    A["JSON of unknown structure"] --> B{"Is the exact shape known?"}
    B -->|No, one-off exploration| C["dynamic / JsonElement.GetProperty"]
    B -->|Yes, or it can be modeled| D["Typed classes + JsonSerializer.Deserialize-T-"]
    D --> E["Type safety, autocomplete, better performance"]

Common Mistakes and Tips

  • Confusing dynamic with var: var is compile-time type inference (the type stays fixed); dynamic gives up type checking entirely, and the same variable can change type at runtime.
  • Using dynamic to avoid writing a class: modeling a stable data structure with a class (like ExternalBookMetadata) almost always pays for its upfront effort, in exchange for type safety and autocomplete for the rest of the code's life.
  • Letting dynamic spread through domain code: a single dynamic in a method signature forces all the code that uses it afterward to also work without type checking; keep dynamic isolated at the system's edge (where it's genuinely needed, like COM interoperability) and convert to concrete types as soon as possible.
  • Not testing dynamic's error path: since the compiler warns of nothing, a typo in a property (document.GetProperty("ivbn")) is only discovered by running that exact line; without tests covering that path, the error can go unnoticed until production.
  • Tip: if you catch yourself writing dynamic in the domain code of an application like BiblioTech, it's a sign that a class is probably missing, not a reason to keep going with dynamic.

Exercises

  1. Declare a variable dynamic value holding the text "BiblioTech", print value.Length, and then reassign it the number 2026. Explain (in a comment) why that reassignment compiles without error, unlike trying the same thing with a var variable.

  2. Using JsonSerializer.Deserialize<JsonElement> and a dynamic variable, extract the first element of the "genres" array from the example JSON in section 7 (hint: document.GetProperty("genres")[0].GetString()).

  3. Explain, in a short paragraph, why turning Book into dynamic Book (that is, declaring dynamic book1 = new Book(...) instead of Book book1 = new Book(...)) in BiblioTech would be a bad design decision, citing at least two of the reasons covered in section 6.

Solutions

dynamic value = "BiblioTech";
Console.WriteLine(value.Length); // 10

value = 2026; // Compiles: dynamic fixes no concrete type, it can hold any value
// With "var value = "BiblioTech";", "value = 2026;" would be a COMPILATION error,
// because var infers string once and the type stays fixed forever.
using System.Text.Json;

string unknownJson =
    """
    {
      "isbn": "978-84-376-0495-4",
      "publisher": { "name": "Sudamericana", "country": "Argentina" },
      "genres": ["Fiction", "Latin American Literature"]
    }
    """;

JsonElement root = JsonSerializer.Deserialize<JsonElement>(unknownJson);
dynamic document = root;

Console.WriteLine(document.GetProperty("genres")[0].GetString()); // "Fiction"

Turning Book into dynamic would remove compile-time checking from all the code that uses book1 (an error like book1.Isbnn — with a typo — would go from an immediate compilation error to a runtime exception, possibly in production); and the IDE would stop being able to offer autocomplete on its properties and methods, since it couldn't know in advance what members book1 would have. On top of that, every access to its members would be slower, being resolved dynamically on every call instead of already resolved by the compiler.

Conclusion

In this lesson you've seen the dynamic type: how it defers all type checking to runtime, how it differs from object (which requires an explicit cast) and from var (which infers a fixed type at compile time), what ExpandoObject is, and in which real scenarios (COM interoperability, untyped JSON, scripting) it makes sense to use it. You've also confirmed why BiblioTech's typed domain should keep relying on concrete classes like Book instead of dynamic, and seen an example of exploring untyped JSON versus the typed approach already used in Module 5.

The next lesson, Memory Management and Garbage Collection, leaves the type system behind to look at how .NET manages memory for any object — typed or dynamic alike: the difference between the stack and the heap, how the garbage collector decides when to free an object, and how to explicitly release external resources (files, connections) with IDisposable, picking back up the using pattern already seen in Module 5.

© Copyright 2026. All rights reserved