C# Anonymous Type Properties: Syntax and Behavior
c# anonymous type properties: Understand how C# anonymous type properties work: declaration, read-only behavior, equality, LINQ usage, and practical limitations.
Anonymous types in C# are a convenient way to create an object with a set of read-only properties without writing a named class. The compiler generates a type behind the scenes, and the properties are inferred from the initializer expression. This article explains how c# anonymous type properties behave, how to use them effectively, and where they fall short.
How Anonymous Type Properties Are Declared
The syntax for an anonymous type uses the new keyword followed by an object initializer with property assignments. Each property can be assigned an explicit name or inferred from the variable name.
var person = new { Name = "Alice", Age = 30 }; var fromVariables = new { name, age }; // property names become "name" and "age"
The compiler infers the property type from the expression. If you use a variable, the property name matches the variable name unless you override it. The properties are read-only: you cannot assign to them after creation.
Read-Only Nature and Compiler-Generated Types
Every anonymous type property is read-only. The compiler generates a class with internal accessibility, and the properties have only getters. This means the object is immutable after construction. The compiler also overrides ToString, Equals, and GetHashCode to provide value-based equality.
var a = new { X = 1, Y = 2 }; var b = new { X = 1, Y = 2 }; Console.WriteLine(a.Equals(b)); // True
Because the type is compiler-generated, you cannot explicitly declare a variable of that type. You must use var or rely on type inference. This has implications for method signatures and fields, as anonymous types cannot be used as return types or field types.
Using Anonymous Types in LINQ Queries
Anonymous types are commonly used in LINQ projections to shape query results. You can select a subset of properties from an entity without creating a named DTO.
var result = from p in products select new { p.Name, p.Price };
The resulting IEnumerable contains objects with Name and Price properties. This keeps the query concise and avoids defining a separate class for the projection.
Limitations: When Anonymous Types Fall Short
The main limitation is that anonymous types are not first-class types you can reference. They cannot be used as method parameters, return types, or class fields. If you need to pass the object around, you must either use a named type or a tuple. Additionally, anonymous types are internal to the assembly, so they cannot cross assembly boundaries.
Another limitation is that you cannot add methods to anonymous types. They only contain the properties you define. If you need behavior, you need a named class.
Equality and Hashing Behavior
The compiler-generated Equals and GetHashCode compare each property value. This means two anonymous objects with the same property names and values are equal. This is useful in LINQ operations like Distinct or Union, but it also means that equality depends on the property values, not reference identity.
var one = new { Value = 10 }; var two = new { Value = 10 }; Console.WriteLine(one == two); // False, because == is reference equality Console.WriteLine(one.Equals(two)); // True
Note that == is not overridden; only Equals is. So you should use Equals when comparing anonymous objects.
Practical Considerations for Maintainability and Performance
Using anonymous types can reduce boilerplate, but it can also obscure the shape of data. When the same projection is used in multiple places, a named type improves clarity and allows reuse. Performance-wise, anonymous types are not significantly different from named types; the compiler generates a class, and property access is direct. However, if you use reflection to inspect anonymous types, that adds overhead, so avoid reflection-heavy scenarios.
Alternatives: Named Types, Tuples, and Records
When you need a type that can be returned from a method or stored in a field, consider using a named class, a System.Tuple, or a C# record. Tuples provide lightweight mutable or immutable data carriers, but they lack named properties unless you use named tuple elements. Records offer value equality and are more expressive for DTOs.
public record Person(string Name, int Age);
Records are a better fit when you need a reusable type with value semantics. Anonymous types remain useful for quick, local projections where a named type would be overkill.