Back to Blog
C#

C# Anonymous Type vs Record: Practical Comparison

c# anonymous type vs record: Compare C# anonymous types and records: syntax, equality, immutability, and when to choose each for data modeling.

C#anonymous typesrecordsdata modelingC# language features
Illustration comparing C# anonymous type and record data structures with a balance scale and code blocks.

When you need to group a few values without defining a full class, C# offers two lightweight options: anonymous types and records. Both provide concise syntax and value-based equality, but they serve different purposes. Understanding c# anonymous type vs record is a matter of knowing how each behaves at compile time and runtime.

Syntax and Declaration Differences

The most immediate difference is how you declare them. An anonymous type uses the new keyword with an object initializer and no type name:

var person = new { Name = "Alice", Age = 30 };

The compiler infers the type, which is only accessible through the var keyword. You cannot write a method that accepts an anonymous type by name, nor can you return one from a method without resorting to object or a generic helper.

A record, on the other hand, is a named type. You declare it explicitly:

public record Person(string Name, int Age);

You can then use Person as a parameter, return type, or field. This simple naming difference has far-reaching consequences for how the type integrates with the rest of your codebase.

Type Identity and Naming

Anonymous types are compiler-generated. Each distinct set of property names and types produces a separate internal type. Two anonymous types with the same shape are the same type only within the same assembly. If you try to pass an anonymous type across an assembly boundary, you lose the type information unless you use reflection or dynamic.

Records are ordinary named types. They can be public, internal, or private, and they appear in IntelliSense and documentation. You can use them in method signatures, generic constraints, and pattern matching. This makes records suitable for public APIs and for data that must be exchanged between modules.

Equality Semantics

Both anonymous types and records implement value-based equality, but the implementation differs. Anonymous types override Equals and GetHashCode based on the values of their properties. Two anonymous type instances with the same property values are equal, but only if they are the same compiler-generated type.

Records provide a more robust equality model. They implement IEquatable<T>, and the compiler generates Equals, GetHashCode, and the == and != operators. Records also support with expressions for non-destructive mutation, which is useful when you need a copy with one field changed:

var olderPerson = person with { Age = 31 };

This is not possible with anonymous types. If you need to modify a value, you must create a new anonymous type manually.

Mutability and Immutability

Anonymous types are always immutable. Their properties are read-only and can only be set during initialization. This is useful for transient data that should not change.

Records are immutable by default when declared with positional parameters, but you can make them mutable by using init or set accessors. For example:

public record MutablePerson { public string Name { get; set; } public int Age { get; set; } }

The default record behavior is designed to encourage immutability, but the language gives you flexibility. Anonymous types offer no such choice; they are immutable by design.

Use Cases: When to Choose Each

Anonymous types shine in LINQ queries and local projections. When you only need a shape to carry data from one part of a method to another, they reduce boilerplate. For example:

var result = people .Where(p => p.Age > 18) .Select(p => new { p.Name, p.Age });

Here, the anonymous type is a convenient container for the projected fields. You never need to name it, and it disappears after the method scope.

Records are better when the data structure has a longer lifespan or crosses boundaries. Use records for DTOs, domain events, API responses, or any data that needs to be compared, serialized, or passed between layers. Records also integrate with System.Text.Json and other serializers without extra configuration, whereas anonymous types are often awkward to serialize because they are internal and lack a public constructor.

Performance and Runtime Considerations

Both anonymous types and records are reference types (unless you use a record struct). The runtime cost of allocation is similar. The main performance difference comes from equality. Anonymous types compute equality by comparing each property directly, which is efficient but does not implement IEquatable<T>. Records generate a more optimized equality implementation using EqualityComparer<T>.Default and also support IEquatable<T> to avoid boxing. For most scenarios, the difference is negligible.

Records also support record struct for value-type semantics, which can reduce heap allocations in hot paths. Anonymous types are always reference types. If you need a lightweight immutable value type, a record struct is a better choice than an anonymous type.

Compatibility and Maintainability

Anonymous types are confined to the assembly in which they are declared. They are also difficult to use with reflection-based frameworks because their names are compiler-generated and not stable. Records, being named types, are easy to document, test, and refactor. You can add attributes, implement interfaces, and extend them with additional members.

Maintainability also improves with records because they are explicit. When you see a record declaration, you know exactly what properties exist and what behavior is inherited. Anonymous types hide their shape, which can make code harder to understand when the projection is complex or reused in multiple places.

FeatureAnonymous TypeRecord
Named typeNo (compiler-generated)Yes
Immutable by defaultYesYes (with positional syntax)
with expressionNoYes
Cross-assembly usageNoYes
Serialization supportLimitedGood (with System.Text.Json)
Use in method signaturesNoYes

Making the Choice

The decision between an anonymous type and a record comes down to scope and intent. If the data is temporary, local to a method, and only used for a quick projection, an anonymous type is sufficient. If the data represents a meaningful concept in your domain, will be passed between methods or assemblies, or needs to support equality and mutation in a controlled way, a record is the better fit.

There is also a middle ground: you can use a record as a local type with record declarations inside a method, but that is rarely necessary. In practice, records are the default choice for any data shape that outlives a single expression.

When you are designing a public API or a data model that will be shared, records give you the compile-time safety and discoverability that anonymous types cannot provide. For one-off transformations inside a query, anonymous types keep the code concise without adding a named type to your project.

c# anonymous type vs record: Practical Usage and Code Exampl | RYUSLOG DEV