Back to Blog
C#

C# Dynamic vs Var: Type Safety and Runtime Behavior

c# dynamic vs var: Understand the real differences between dynamic and var: compile-time type inference, runtime binding, performance implications, and when to choose...

C#dynamic keywordtype inferencestatic typingruntime binding
Illustration comparing compile-time type inference with runtime dynamic binding in C#.

c# dynamic vs var requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you write var in C#, the compiler infers the type from the initializer and locks it in at compile time. With dynamic, the type is resolved at runtime, and the compiler performs no static checks on operations involving that value. That singular difference determines everything else: performance, type safety, API design, and testing.

var x = 10; // x is int, fixed at compile time x = "hello"; // compile error: cannot implicitly convert string to int dynamic d = 10; // d's type is resolved at runtime d = "hello"; // allowed, but now d is a string at runtime

In the first line, var produces an int variable. Attempting to assign a string later is a compile-time error. In contrast, dynamic does not post any compile-time type; the actual operation d = "hello" is valid because the binding happens at runtime, and the assignment succeeds if the runtime type supports it.

Type Safety: Compile-Time vs Runtime

The key tradeoff between c# dynamic vs var is where type errors surface. var gives you compile-time type safety because the compiler infers the exact type from the initializer. Any invalid operation is caught when the code is compiled. dynamic defers all binding to runtime, so errors like calling a method that does not exist are only caught when that line executes.

Consider a method that returns an object:

object obj = GetData(); // obj is object, but you can't access properties without casting // (string)obj would throw if obj isn't a string. var v = GetData(); // v is statically typed as whatever GetData returns // If GetData returns an int, v is int. dynamic dyn = GetData(); // dyn is dynamic, no compile-time type // If GetData returns an int, dyn.Length would throw at runtime because int has no Length property.

With var, the compiler knows the declared type, so IntelliSense and refactoring tools work as expected. With dynamic, you lose IntelliSense because the compiler doesn't know the type until execution.

The Hidden Cost: Dynamic Runtime Binding

Using dynamic has a performance overhead. The runtime must generate a call site, determine the actual type of the object, and then perform the operation. For single calls, the overhead is small, but in loops or frequently called code, it adds up. Reflection is even slower, but dynamic uses the DLR (Dynamic Language Runtime) which caches call sites, making repeated calls faster than raw reflection.

List<Person> people = GetPeople(); dynamic sum = 0; foreach (var p in people) { sum += p.Age; // dynamic binding adds overhead }

If Person.Age is an int, the addition is still performed at runtime, and the DLR must infer the operator's behavior. A straightforward int loop would be faster. However, the actual difference depends on the operation's complexity and the number of iterations.

When to Use var

var is designed for readability when the type is obvious from the initializer. Use it in these cases:

  • When the right side is a constructor call: var list = new List<int>();
  • When the initializer is an anonymous type: var item = new { Name = "Alice", Age = 30 };
  • When the method name clearly indicates the return type: var file = File.Open("temp.txt", FileMode.Create);

The principle is to use var when it improves clarity without hiding the type. If the type isn't evident from the initializer, a developer may need to hover over the variable to understand it. For instance, var result = GetEmployeeDetails(); is useless if GetEmployeeDetails returns an obscure type.

When to Use dynamic

dynamic is needed for scenarios where compile-time types are insufficient:

  • Interacting with COM objects like Office interop, where the APIs were written for dynamic languages.
  • Calling methods on objects from dynamic languages (e.g., IronPython) where the shape isn't known until runtime.
  • Working with JSON or other serialized data where the schema is dynamic.
  • When reflection would be overly verbose; dynamic can simplify member access.
dynamic obj = GetJsonObject(); string name = obj.name; // accesses property 'name' at runtime

Without dynamic, you might need to use reflection or parse the JSON into a strongly typed object first.

Performance Implications and Caching

The DLR does cache call sites for simple operations. Once a call site is bound to a given type, subsequent calls with the same types are faster. However, if the runtime types vary frequently, the cache is ineffective, and each call incurs a binding overhead.

For example, a method that receives a dynamic parameter and is called many times with different runtime types will see erratic performance. If the types are consistent, the performance is acceptable for most scenarios but still slower than static dispatch. Use dynamic sparingly in hot paths.

Errors in Dynamic Code Are Runtime Exceptions

A common mistake is to assume that code that compiles is correct. With dynamic, many errors only appear when the method runs. A typo in a property name, a wrong argument type, or a missing method will result in a Microsoft.CSharp.RuntimeBinder.RuntimeBinderException at runtime.

static void PrintName(dynamic person) { Console.WriteLine(person.FirstName); } var alice = new { FirstName = "Alice" }; // this is fine PrintName(alice); // works var bob = new { LastName = "Bob" }; // no FirstName property PrintName(bob); // throws RuntimeBinderException at runtime

Because PrintName is compiled without knowing the type of person, the call to FirstName is bound at runtime. Passing bob fails, but the failure occurs only when the method is invoked, not when the code is compiled.

Dynamic and Overload Resolution

Overload resolution with dynamic is deferred to runtime as well. If a method has overloads, the runtime selects the one that matches the actual types of the arguments. This can lead to surprising behavior if the runtime types don't match the intended overload.

void Process(int value) { } void Process(string value) { } void Call(dynamic argument) { Process(argument); } Call(1); // calls Process(int) Call("x"); // calls Process(string)

At compile time, Call is valid because the method call to Process is bound with dynamic. The runtime picks the correct overload based on the actual argument type. If no overload matches, a RuntimeBinderException occurs.

Compatibility: Dynamic and Language Features

Not all C# features can be used with dynamic. For instance, you cannot use extension methods on dynamic objects because extension methods require compile-time knowledge of the type, which dynamic doesn't provide. Similarly, lambda expressions and delegates cannot be directly applied to dynamic values in all scenarios.

dynamic d = GetValue(); d.SomeExtensionMethod(); // compile error: cannot use extension methods on dynamic var list = new List<int>(); var hasAny = list.Any(); // fine dynamic dynList = new List<int>(); var hasAny2 = dynList.Any(); // compile error

To use an extension method with dynamic, you would need to cast it to the underlying static type first, which defeats the purpose of using dynamic.

Decision Criteria: Choosing Between dynamic and var

Use var unless you have a concrete reason to use dynamic. The defaults are:

  • Use var for local variable declarations where the type is obvious and you want to avoid repeating it.
  • Use dynamic only when the static type is unavailable or impossible to express at compile time.

Choose var if:

  • You want compile-time type checking.
  • You want better performance.
  • You want IntelliSense support.
  • The type is known from the initializer.

Choose dynamic if:

  • You are interoperating with a dynamic language or COM object.
  • You must handle inherently dynamic data (JSON with unknown structure).
  • You want to simplify reflection-based code, accepting runtime binding and potential runtime errors.

Avoid dynamic in public APIs when a strongly typed alternative exists. An API that accepts dynamic pushes the burden of correctness onto the caller and makes the contract unclear.

c# dynamic vs var: Practical Usage and Code Examples | RYUSLOG DEV