Back to Blog
C#

c# var vs object: Static Type vs Base Type

c# var vs object: The real difference between var and object in C#: compile-time type inference, boxing behavior, and when each declaration is the right choice.

C#type inferenceboxingstatic typingcompile-time checkingvariable declarations
Editorial illustration comparing C# var compile-time type inference with object base-type boxing, showing a typed variable next to a boxed value.

The core of c# var vs object is what the compiler knows about a variable's type. var is a compile-time type inference mechanism, while object is the base type of every type in the C# type system.

When you write:

var number = 42;

The compiler determines that number is an int. The compiled IL is identical to writing int number = 42;. There is no runtime lookup, no dynamic dispatch, and no type erasure. The variable has a fixed static type from the moment it is declared.

This is the most common misunderstanding: developers sometimes assume var behaves like JavaScript's let or Python's dynamic typing. It does not. Once the compiler infers the type, that type is locked in. You cannot later assign a different type to the same variable:

var value = 42; value = "hello"; // Compile-time error: cannot convert string to int

The error appears at compile time, which is exactly the point. The compiler enforces the inferred type as strictly as it would if you had written int value = 42;.

What object Actually Is

object is the base type of every type in the C# type system. Every class, struct, enum, and delegate ultimately derives from object. When you declare a variable as object, you are deliberately choosing to store a value whose static type is the most general type available.

object value = 42;

This declaration is valid, but it changes what the compiler knows about value. The static type is object, so you cannot call int-specific members without a cast:

object value = 42; int result = value + 1; // Compile-time error

The compiler sees value as object, and object does not define an addition operator. To perform arithmetic, you must cast:

object value = 42; int result = (int)value + 1;

This cast is an unboxing operation when the underlying value is a value type. Unboxing copies the value out of the boxed object and requires the cast to match the actual runtime type. If the runtime type is not int, the cast throws InvalidCastException.

The Static Type Determines What You Can Call

The central difference between var and object is what the compiler knows about the variable's type. This affects which members you can access without a cast, which overloads the compiler selects, and whether value types get boxed.

DeclarationStatic typeCompile-time member accessRuntime representation
var x = 42;intFull int members availableDirect value, no boxing
object x = 42;objectOnly object membersBoxed on the heap
object x = "text";objectOnly object membersReference, no boxing
var x = "text";stringFull string membersReference, no boxing

With var, the compiler keeps the full static type information. With object, the static type is erased down to the base type. This is why code that uses object often contains casts that would be unnecessary with var.

Boxing and the Cost of object

When you assign a value type to an object variable, the runtime boxes the value. Boxing allocates a new object on the heap and copies the value into it. This has two consequences: a heap allocation and a copy.

int number = 42; object boxed = number; // Boxing: heap allocation + copy int unboxed = (int)boxed; // Unboxing: copy back to the stack

With var, no boxing occurs because the variable keeps its original value type:

int number = 42; var copy = number; // No boxing, no heap allocation

The practical impact depends on how often the operation runs. A single boxing operation in a rarely executed path is irrelevant. Boxing inside a hot loop, however, creates allocation pressure on the garbage collector. If you find yourself repeatedly boxing and unboxing the same value, object is the wrong container.

The same reasoning applies to collections. A List<object> that stores integers boxes every element. A List<int> does not. This is a measurable difference in both memory and allocation rate, and it is a common reason developers switch from object-based containers to generic ones.

Where var Cannot Be Used

var is restricted to local variables. You cannot use it for fields, properties, method parameters, or return types:

var field = 42; // Compile-time error public var Method() { } // Compile-time error

object, by contrast, can be used anywhere a type is expected. This makes object the only choice when you need a general-purpose container at the type level, such as a field that must hold values of different types.

There is also a constraint on var initialization: the right-hand side must provide a type. You cannot write var x; without an initializer, because the compiler has nothing to infer from. object x; is valid because the type is explicitly declared.

When Each Declaration Is the Right Choice

Use var when the inferred type is clear from the right-hand side and you want the compiler to keep full type information. This is common with constructor calls, LINQ projections, and generic method results:

var customers = GetCustomers(); // Type is clear from method name var filtered = customers.Where(c => c.Age > 18); // LINQ result type is verbose var dictionary = new Dictionary<string, List<int>>(); // Long type name

Use object only when you genuinely need to treat values as having no specific static type. This happens in reflection code, in interop scenarios, and in APIs designed before generics existed. In modern C#, most of those cases have better alternatives: generics for type-safe containers, dynamic for late-bound dispatch, and interfaces when you only need a specific contract.

A common rule of thumb: if you declare a variable as object and immediately cast it back to its real type, you are paying the cost of type erasure without gaining anything. The cast is required precisely because the static type was discarded.

The Misconception That var Is Dynamic

Some developers avoid var because they believe it behaves like dynamic. The two are unrelated. dynamic defers all binding to runtime and has real performance and safety costs. var is fully resolved at compile time and produces the same IL as an explicit type declaration.

dynamic d = 42; d.SomeMethod(); // Resolved at runtime; throws if method does not exist var v = 42; v.SomeMethod(); // Compile-time error: int has no SomeMethod

The difference is visible in the error behavior. dynamic delays the failure until execution. var reports it at compile time. For most application code, compile-time checking is preferable because it catches mistakes earlier and makes the codebase easier to refactor.

Choosing Between var and object in Real Code

The decision is rarely between var and object in isolation. In practice, you choose between var, an explicit type, or a more specific type than object. The question is what the compiler should know about the variable.

If the variable holds a single well-defined type, use var or the explicit type. The choice between those two is stylistic. If the variable must hold values of different types, object is one option, but consider whether an interface, a base class, or a generic type better expresses the contract you actually need.

For example, a method that accepts any value and writes it to a log can reasonably take object:

public void LogValue(object value) { Console.WriteLine(value?.ToString()); }

Here object is appropriate because the method only needs ToString(), which is defined on object. No cast is needed, and the parameter genuinely accepts any type.

The same method written with var would not compile, because var cannot be used for parameters. And writing it with a generic parameter would add complexity without benefit:

public void LogValue<T>(T value) // Works, but adds nothing over object here

The generic version is only useful when the method needs to use the type T in a meaningful way, such as returning it or using it in a constraint.

What the Compiler Does With Each Declaration

Understanding the compiled behavior helps clarify the difference. For var, the compiler replaces the inferred type directly. For object, the compiler inserts boxing or reference conversion at the assignment point.

// Source var number = 42; object boxed = number; // Equivalent after type resolution int number = 42; object boxed = (object)number; // Boxing conversion

The second line is where the allocation happens. The compiler does not insert boxing for var because the type never changes. This is why var is not a performance concern and object can be one when used carelessly with value types.

For reference types, object does not cause boxing. A string stored in an object variable is just a reference. The cost is the loss of static type information, not an allocation. This distinction matters when evaluating whether object is acceptable in a given code path: reference types pay the type-erasure cost, value types pay both type erasure and boxing.

c# var vs object: What the Compiler Actually Knows | RYUSLOG DEV