Back to Blog
C#

C# Anonymous Type with LINQ: Projection and Usage

c# anonymous type with linq: Learn how to use C# anonymous types in LINQ queries for projections, grouping, and joins, including type inference, limitations, and when...

anonymous typesLINQC# programmingtype inferencequery projection
Diagram showing a LINQ query projecting data into an anonymous type with inferred properties.

When you write a LINQ query that needs to reshape data, the C# anonymous type with LINQ is often the fastest way to project a subset of properties without declaring a new class. Anonymous types rely on compiler-generated type inference, which makes them convenient inside query expressions, but that same convenience comes with constraints that affect where they can be used.

Creating an Anonymous Type in a Select Clause

The most common use of an anonymous type in LINQ is a Select projection that picks specific fields from a source object. The compiler generates a new type with read-only properties matching the names and types you specify.

var products = new[] { new { Id = 1, Name = "Laptop", Price = 1200m, Quantity = 2 }, new { Id = 2, Name = "Mouse", Price = 25m, Quantity = 5 } }; var result = products.Select(p => new { p.Id, p.Name, Total = p.Price * p.Quantity }); foreach (var item in result) { Console.WriteLine($"{item.Name}: {item.Total:C}"); }

The new { p.Id, p.Name, Total = ... } expression creates an anonymous type. The property names Id and Name are inferred from the member access, while Total is explicitly named. The compiler infers the type of Total from the multiplication result, which is decimal here. This projection avoids creating a separate DTO class for a one-off query.

How the Compiler Infers Property Names and Types

Anonymous type property names are inferred from the expression used in the initializer. If you write new { p.Id }, the property is named Id. If you write new { Total = p.Price * p.Quantity }, the property is named Total because you explicitly provide the name. You can also use a variable name directly: var name = "x"; new { name } results in a property named name.

The compiler generates a class with get-only properties for each initializer. The type of each property is the compile-time type of the expression. This means the anonymous type is strongly typed within the scope where it is used, and IntelliSense and compile-time checking work normally.

Using Anonymous Types in Where, OrderBy, and GroupBy

Anonymous types are not limited to Select. They are often used as keys in GroupBy or OrderBy when you need to group by multiple fields.

var orders = new[] { new { CustomerId = 1, Region = "West", Amount = 100m }, new { CustomerId = 2, Region = "East", Amount = 150m }, new { CustomerId = 1, Region = "West", Amount = 75m } }; var groups = orders.GroupBy(o => new { o.CustomerId, o.Region }); foreach (var group in groups) { Console.WriteLine($"{group.Key.CustomerId} / {group.Key.Region}: {group.Sum(o => o.Amount)}"); }

Here the anonymous type serves as a composite key. The GroupBy method compares the keys using the default equality comparer, which for anonymous types compares each property value. This works because the compiler generates Equals and GetHashCode implementations based on the property values.

You can also use anonymous types in OrderBy when you want to sort by multiple criteria without creating a custom comparer:

var sorted = orders.OrderBy(o => new { o.Region, o.Amount });

The compiler generates a comparer that sorts by Region first, then by Amount, because anonymous types implement IComparable? Actually, they do not. The default comparer for anonymous types uses Comparer<object>.Default, which compares property values in order. This works for simple types, but for complex types you may need an explicit comparer. In practice, using anonymous types for sorting is less common than using ThenBy.

Limitations: Immutability, Scope, and Method Returns

Anonymous types have several constraints that matter when you design your code.

  • Immutability: All properties are read-only. You cannot assign to them after creation. If you need a mutable projection, you must use a named class.
  • Scope: Anonymous types are only usable within the method or expression where they are defined. You cannot declare a field, property, or method parameter with an anonymous type because the type has no name.
  • Method returns: You cannot return an anonymous type from a method as a strongly typed value. You could return object or dynamic, but that loses compile-time type safety and is rarely worth the tradeoff.
  • Type identity: Two anonymous types with the same property names and types, in the same assembly, are the same type. This allows you to use them across multiple LINQ expressions in the same method, but not across methods unless you pass them as object.

These limitations are not bugs; they are intentional design choices that keep the feature lightweight. When you need to pass a projected shape outside the current scope, you should define a named type.

Anonymous Types vs. Named DTOs

Choosing between an anonymous type and a named DTO depends on how the data will be used. The following table summarizes the key differences.

CriterionAnonymous TypeNamed DTO
Type nameCompiler-generatedExplicitly defined
ReusabilityLimited to current scopeCan be used across methods and layers
MutabilityRead-only propertiesCan have settable properties
MaintainabilityChanging shape breaks all usage sitesChanges are localized to the type
PerformanceSlight overhead from compiler-generated classNo additional overhead
Best fitOne-off query projectionData transfer across boundaries

Use an anonymous type when the projection is local to a single query and you do not need to pass it to another method. Use a named DTO when the shape represents a contract between layers, such as a service returning data to a controller, or when you need to modify the values after creation.

Performance and Memory Considerations

Anonymous types are reference types, so each instance is allocated on the heap. The compiler generates a class with auto-properties and overrides for Equals, GetHashCode, and ToString. This adds a small amount of metadata compared to a hand-written DTO, but the runtime cost is negligible for typical LINQ workloads.

One subtle performance point is that the compiler generates a new anonymous type for each distinct property shape in an assembly. If you create many different projections, you get many generated classes. This does not affect runtime speed, but it can slightly increase assembly size. In practice, this is rarely a concern.

There is no reflection involved when you use anonymous types directly. Reflection only appears if you cast them to object and inspect properties dynamically, which you should avoid unless necessary.

Compatibility and Maintainability: When the Shape Changes

A common maintenance issue with anonymous types is that the property names are part of the type identity. If you rename a property in the initializer, every code that accesses that property must be updated. The compiler will catch these errors, which is good, but it means the type is tightly coupled to the query that creates it.

For example, if you change new { p.Id, p.Name } to new { p.Id, p.Title }, any subsequent code using item.Name will fail to compile. This is actually a benefit because it surfaces the change at compile time, but it also means you cannot reuse the same anonymous type across different queries if the property names differ.

When the shape of the projected data is likely to change or is shared across multiple layers, a named DTO provides a stable contract. You can add or remove properties without affecting the consumers, as long as the required ones remain.

Practical Example: Joining Two Collections with Anonymous Types

Anonymous types are especially useful in Join operations where you need to combine fields from two sources without creating a dedicated result class.

var customers = new[] { new { Id = 1, Name = "Alice" }, new { Id = 2, Name = "Bob" } }; var orders = new[] { new { CustomerId = 1, Product = "Laptop" }, new { CustomerId = 2, Product = "Mouse" }, new { CustomerId = 1, Product = "Keyboard" } }; var joinResult = customers.Join( orders, c => c.Id, o => o.CustomerId, (c, o) => new { c.Name, o.Product }); foreach (var item in joinResult) { Console.WriteLine($"{item.Name} bought {item.Product}"); }

The lambda (c, o) => new { c.Name, o.Product } creates an anonymous type that combines the customer name and the product name. This keeps the query self-contained and avoids polluting the codebase with a class that is only used once. If the join result needs to be returned from a method, you would replace the anonymous type with a named class.

Anonymous types also work well in SelectMany and other LINQ operators where you need to carry intermediate state. The compiler-generated Equals and GetHashCode implementations make them suitable for Distinct and GroupBy operations without extra code.

When you use anonymous types in LINQ, you get concise, readable queries that focus on the data shape you need. The key is to recognize when the convenience of an anonymous type outweighs the benefit of a named type. For local projections and composite keys, anonymous types are a natural fit. For data that crosses method boundaries or represents a stable contract, a named DTO is the safer choice.

c# anonymous type with linq: Practical Usage and Code Exampl | RYUSLOG DEV