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
- The
dynamictype: resolution at runtime dynamicversusobject: same data, checked at different timesdynamicversusvar: dynamic isn't the same as inferredExpandoObject: dynamic objects with no defined class- When it makes sense to use
dynamic - Why to avoid
dynamicin BiblioTech's domain - Example: reading a JSON of unknown structure with
dynamic, versus the typed approach
- The
dynamic type: resolution at runtime
dynamic type: resolution at runtimeA 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 propertyThe 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.
dynamic versus object: same data, checked at different times
dynamic versus object: same data, checked at different timesobject (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 runtimeobject |
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.
dynamic versus var: dynamic isn't the same as inferred
dynamic versus var: dynamic isn't the same as inferredIt'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 momentvar 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 |
ExpandoObject: dynamic objects with no defined class
ExpandoObject: dynamic objects with no defined classExpandoObject (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.
- When it makes sense to use
dynamic
dynamicdynamic 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 |
- Why to avoid
dynamic in BiblioTech's domain
dynamic in BiblioTech's domainBiblioTech'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.Isbnfails immediately while writing the code, with a clear message, ifIsbndidn't exist or were misspelled; withdynamic 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 adynamicvariable, 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
dynamicis resolved dynamically at runtime (through a resolution pipeline and an internal DLR cache), slower than the direct, already-resolved call on a static type likeBook.
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.
- Example: reading a JSON of unknown structure with
dynamic, versus the typed approach
dynamic, versus the typed approachThe 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
dynamicwithvar:varis compile-time type inference (the type stays fixed);dynamicgives up type checking entirely, and the same variable can change type at runtime. - Using
dynamicto avoid writing a class: modeling a stable data structure with a class (likeExternalBookMetadata) almost always pays for its upfront effort, in exchange for type safety and autocomplete for the rest of the code's life. - Letting
dynamicspread through domain code: a singledynamicin a method signature forces all the code that uses it afterward to also work without type checking; keepdynamicisolated 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
dynamicin the domain code of an application like BiblioTech, it's a sign that a class is probably missing, not a reason to keep going withdynamic.
Exercises
-
Declare a variable
dynamic valueholding the text"BiblioTech", printvalue.Length, and then reassign it the number2026. Explain (in a comment) why that reassignment compiles without error, unlike trying the same thing with avarvariable. -
Using
JsonSerializer.Deserialize<JsonElement>and adynamicvariable, extract the first element of the"genres"array from the example JSON in section 7 (hint:document.GetProperty("genres")[0].GetString()). -
Explain, in a short paragraph, why turning
Bookintodynamic Book(that is, declaringdynamic book1 = new Book(...)instead ofBook 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.
C# Programming Course
Module 1: Introduction to C#
- Introduction to C#
- Setting Up the Development Environment
- Hello World Program
- Basic Syntax and Structure
- Variables and Data Types
- Arrays and Strings
Module 2: Control Structures
Module 3: Object-Oriented Programming
- Classes and Objects
- Methods
- Constructors and Destructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- Structs and Records: Value Types and Reference Types
Module 4: Advanced C# Concepts
- Interfaces
- Delegates and Events
- Pattern Matching and Modern C# Features
- Generics
- Collections
- LINQ (Language Integrated Query)
- Asynchronous Programming
Module 5: Working with Data
- File I/O
- Serialization
- Database Connectivity
- Entity Framework
- Working with JSON and Consuming REST APIs
Module 6: Advanced Topics
- Reflection
- Attributes
- Dynamic Programming
- Memory Management and Garbage Collection
- Multithreading and Parallel Programming
Module 7: Building Applications
Module 8: Best Practices and Design Patterns
- Coding Standards and Best Practices
- Design Patterns
- Dependency Injection and Inversion of Control
- Unit Testing
- Code Review and Refactoring
