AlgoMaster Logo

Anonymous Types

Last Updated: May 17, 2026

9 min read

An anonymous type is a small, readonly class the compiler generates on the spot when you write new { ... } without a type name. It's useful when you need to bundle a few values together for a short stretch of code, especially when shaping the result of a query, and you don't want to write a whole class just to hold them. This chapter covers the syntax, what the compiler builds behind the scenes, where anonymous types fit, and where they fall short.

What an Anonymous Type Is

The simplest way to see an anonymous type is to make one:

No class declaration anywhere. No Product type defined in the file. Just new { ... } with named properties, and a variable to hold the result.

What actually happens at compile time is that the C# compiler invents a class for you. It picks an unspeakable name (something like <>f__AnonymousType0), gives it two readonly properties (Name of type string and Price of type decimal), generates a constructor that takes both, and overrides Equals, GetHashCode, and ToString based on those properties. From that point on, the variable product is just an instance of that hidden class.

Three things follow from this:

  • They're reference types. Like any class, an anonymous type lives on the heap. Assigning an anonymous-typed variable to another copies the reference, not the data.
  • They're immutable. Each property is read-only. There is no setter. Once the object is constructed, the values can't change.
  • You need `var`. The type name is unspeakable, so you can't write it out. var is the only way to declare a variable that holds one.

A var product = new { ... } declaration is the canonical shape. Forget any one of the three pieces (the var, the new, the { ... }), and you're writing something different.

Property Name Inference

Anonymous types pick up a shortcut for the common case where the property you want to expose comes straight from another variable or member. Instead of writing new { Name = product.Name }, you can write new { product.Name }, and the compiler infers the property name from the source.

The anonymous type has two properties named customerName and cartTotal, exactly matching the variable names. The compiler does this for any expression that ends in an identifier: local variables, parameters, fields, properties, and method-call-free member accesses like product.Price or order.Total.

This makes projections much shorter when you're forwarding existing data:

The listing variable has exactly two properties, both inferred. The full product object has four, but the projection only keeps the two you care about. This pattern is the workhorse of LINQ projections, which the LINQ section covers in depth.

Property name inference only works when the expression is itself an identifier or a member access. If you compute a value, you have to name it explicitly:

Property names within a single anonymous type must be distinct, just like fields in a regular class. You can't have two Price properties.

What the Compiler Generates

It helps to see the equivalent class the compiler builds. If you write:

The compiler generates something roughly equivalent to:

You never see this class in your source code, but it has all the standard plumbing. The ToString override is especially convenient when debugging:

No format string, no custom code. The compiler-generated ToString walks the properties and prints them in a readable form.

The generated class is internal sealed, meaning it lives only inside the assembly that contains the code that created it, and nothing can inherit from it. You can't add methods to it, you can't extend it with extra properties later, and you can't reference it by name from another file.

Here's a diagram of what's happening behind the scenes:

The single literal turns into a full class plus an object of that class. You write four lines and get a fully equipped reference type, complete with value-based equality.

Equality Semantics

Because the compiler overrides Equals and GetHashCode, two anonymous objects compare equal when every property has the same value, not when the two references point to the same heap object. This is structural equality, the same flavor records use.

a and b are two separate heap objects (ReferenceEquals confirms it), but the generated Equals walks both properties, finds them equal, and returns true. a and c differ in Price, so they're not equal.

A second rule follows from how the compiler picks anonymous types: two anonymous objects share the same generated class only when their property names, types, and order match. If you flip the order, you get a different generated type, and Equals returns false because the types don't match.

Property order is part of the type identity. If you want a and b to compare equal, write the properties in the same order in both places.

Why Anonymous Types Exist (Projections)

Anonymous types earn their keep when you need to reshape data temporarily, without bothering to declare a class. The textbook example is a LINQ projection that picks specific columns out of a richer source:

The full Product class has four properties, but the listing only needs two. Declaring a separate ProductListing class with just Name and Price would work, but it'd be a class that exists for one method, used in one loop, and named because the language insists. The anonymous projection drops that ceremony.

The same idea shows up when joining or grouping. Suppose you want a quick summary of orders by customer:

The shape { Customer, Total, Count } only exists between the Select and the foreach. There is no reason to give that shape a name in the type system. Anonymous types let the projection stay focused on what it produces, not on how to declare the holder.

Limitations

Anonymous types are deliberately small. The compiler builds just enough to support the basic projection use case, and not more. The limits matter because they decide when an anonymous type is the right tool and when you need to reach for something else.

You can't write the type name. The generated class has an unspeakable identifier, so you can't declare a field, parameter, or return type using it. This is what forces var for the variable.

They're hard to return from a method. Since you can't name the type, a method that returns an anonymous object has to declare its return type as object, dynamic, or a generic parameter. None of those preserve the property names at the call site, which defeats the point.

You'd have to use reflection or dynamic to read the properties, both of which give up compile-time checking. If a method needs to return a shaped object, declare a real type for it (or a record).

You can't add methods. The generated class has only properties, plus the standard equality and ToString overrides. There's no way to add GetDiscountedPrice() to an anonymous type. If your shape needs behavior, it should be a class or a record.

You can't inherit from them. The generated class is sealed. You can't subclass it, you can't have it implement an interface you define, and you can't add it to a heterogeneous list via a shared base type. Anonymous types are leaf types, not building blocks for hierarchies.

You can't use them across assemblies cleanly. Even though the generated class is internal, two assemblies can each produce anonymous types with the same shape, and they'll have different runtime types. Equality between them fails. Anonymous types are local to one compilation unit by design.

The pattern that captures all of this: use anonymous types where they're created, throw them away in the same method, and don't try to give them a longer life.

Anonymous Types vs Tuples vs Records

C# has three close-by ways to bundle a few values together: anonymous types, value tuples ((decimal Total, int Count)), and records. They overlap, but the trade-offs are real and worth knowing.

FeatureAnonymous typeValueTupleRecord
Declarationnew { Name, Price }(Name, Price)record Listing(string Name, decimal Price);
Type categoryReference (class)Value (struct)Reference (default) or value (record struct)
Property namesRequired, part of typeOptional, not part of typeRequired, part of type
Mutable?NoYes (fields)Init-only by default
EqualityValue, by all propertiesValue, by all fieldsValue, by all properties
Can return from methodOnly as objectYes, fully namedYes, fully named
Can be used across filesNoYesYes
InheritanceNo (sealed)NoYes (with other records)
Add methodsNoNo (helper extensions only)Yes
Heap allocationYes (every new)No (stack, unless boxed)Yes (unless record struct)

Read the table column by column. Anonymous types are the lightest in syntax for a one-off shape, but the most limited in lifespan. Tuples are perfect for returning two or three loosely related values from a method without dragging in a class. Records are the right tool when the shape has a name worth declaring and lives across multiple methods or files.

Here are the same three values expressed all three ways:

The output is the same, but the code shape is different. The anonymous version says "I need a holder, here is one." The tuple version says "I want two values without naming the type." The record version says "this shape has a name and a place in my codebase."

A simple decision rule:

  • If the shape lives inside a single method and just helps Select produce something useful, use an anonymous type.
  • If you want to return two or three values from a method without declaring a type, use a tuple.
  • If the shape has a name worth using elsewhere, or you want methods on it, use a record.

A Worked Example

A small program that pulls the chapter's ideas together. The cart has products with several properties; we project a couple of shaped views for display and reporting, never declaring an extra named class.

Two anonymous types appear. The first, inside Select, has four properties: three forwarded from Product (using property name inference) and one computed (Subtotal, which has to be named explicitly because it's an expression). The second, summary, is a single object that holds three aggregates. Neither shape is worth a named class. They each exist for half a screen of code, then disappear.

If the cart-row shape grew into something the rest of the app needed (say a CartRowDto returned from a service), the right move would be to promote it to a record. That's the natural progression: anonymous in one method, record when it crosses boundaries.

Summary

  • An anonymous type is a compiler-generated, internal sealed class with read-only properties, structural Equals/GetHashCode, and a readable ToString. You create one with new { Property = value, ... }.
  • They are reference types, immutable, and held in variables typed var because the generated class name is unspeakable.
  • Property name inference lets you write new { product.Name, product.Price } and have the compiler use the right-hand identifier as the property name. Computed values still need an explicit name.
  • Two anonymous objects are equal under Equals when they share the generated type (same property names, types, and order) and every property value matches. == falls back to reference equality.
  • The natural home for anonymous types is a LINQ Select that shapes a few values for local consumption. They make projections short without forcing you to declare a one-off class.
  • They can't be the declared return type of a method (you'd lose the names through object), can't add methods, can't inherit, and can't be shared across assemblies cleanly. As soon as a shape outgrows one method, promote it to a record or a class.
  • The rule of thumb across the three close-by tools: anonymous type for one-method shapes, tuple for two-or-three-value returns without behavior, record for shapes with a name worth using elsewhere.