C# Anonymous Types and Anonymous Methods in 2026
C# anonymous types on .NET 10: value equality vs ==, read-only members, with (C# 10), when a tuple or record fits better, and what delegate { } can still do.
Revised November 2026. This tutorial first appeared in Korean in 2021. We recompiled both of its examples on .NET 10.0.14 (C# 14). The anonymous-type example did not compile: the float literals were written as
0.1 fwith a space (error CS1003), andconsole.Writewas lowercase (error CS0103). The anonymous-method example declaredpublic static void main()in lowercase, which builds no program at all (error CS5001). It also claimed the loop printed1,2,3,4,5; it actually prints1 2 3 4 5. Below are the fixed examples, the parts the original skipped (equality,with, the type identity rule), and when to stop using an anonymous type.
This article is for C# developers who see new { … } in LINQ code and delegate { } in older event code and want to know what those two features really guarantee today. Short answer: an anonymous type is a compiler-generated, read-only class with value-based Equals, and it is meant to live and die inside one method. An anonymous method is the C# 2 ancestor of the lambda; in 2026 its only unique trick is leaving out the parameter list. Delegates themselves are covered in C# delegates and events, and lambda syntax in C# lambda expressions. This post does not repeat them.
Anonymous types: the 2021 example, fixed
An anonymous type is a class without a name. You create it with new and an object initializer, and you must hold it in a var, because there is no type name you could write. The 2021 example built one with two values and a second one holding two arrays. After fixing the two typos it reads:
var temp = new { Age = 40, Name = "Mina" };
Console.WriteLine("{0},{1}", temp.Age, temp.Name);
var tempArr = new {
Int = new int[] { 1, 2, 3, 4, 5 },
Float = new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f }
};
foreach (var element in tempArr.Int) Console.Write("{0} ", element);
Console.WriteLine();
foreach (var element in tempArr.Float) Console.Write("{0} ", element);
Console.WriteLine();
40,Mina
1 2 3 4 5
0.1 0.2 0.3 0.4 0.5
As written in 2021 the build stopped with these (first lines shown):
Program.cs(17,21): error CS1003: Syntax error, ',' expected
Program.cs(25,13): error CS0103: The name 'console' does not exist in the current context
0.1 f is the number 0.1 followed by an unknown identifier f, so the compiler expects a comma. The suffix must touch the digits: 0.1f. The original post also opened with a list of declarations including class D;. That is not valid C#: class declares a type, it is not a variable type you can put in front of a name.
The 2021 text said the instance is read-only. That is correct, and here is the actual error:
Program.cs(2,1): error CS0200: Property or indexer '<anonymous type: int Id, string Name>.Id' cannot be assigned to -- it is read only
Two more compile-time rules the original did not mention: a property cannot be initialized with a bare null (new { Tag = null } gives error CS0828, because the compiler cannot infer a type from it), and an implicitly typed array of anonymous objects needs every element to have the same shape, so new[] { new { Id = 1 }, new { Id = 2L } } fails with error CS0826 (int vs long).
Equality, hash codes and the type identity rule
The original described anonymous types only as "handy temporary variables". The details that matter in real code are about equality. We ran this on .NET 10:
var a = new { Id = 7, Name = "Mina" };
var b = new { Id = 7, Name = "Mina" };
var c = new { Name = "Mina", Id = 7 }; // same members, different order
Console.WriteLine(a);
Console.WriteLine($"Equals: {a.Equals(b)} ==: {a == b}");
Console.WriteLine($"same type a/b: {a.GetType() == b.GetType()}, a/c: {a.GetType() == c.GetType()}");
Console.WriteLine($"hash equal: {a.GetHashCode() == b.GetHashCode()}");
Console.WriteLine(a.GetType().Name);
{ Id = 7, Name = Mina }
Equals: True ==: False
same type a/b: True, a/c: False
hash equal: True
<>f__AnonymousType0`2
What this tells you:
EqualsandGetHashCodecompare values. That is why anonymous objects work as composite keys inGroupBy,DistinctandHashSet. AHashSet<object>holdinga,band a third object with a different name had aCountof 2.==does not. The compiler generates no==operator, soa == bis a reference comparison and printedFalse. This is the single most common surprise. Records and tuples behave differently:(Id: 7, Name: "Mina") == (7, "Mina")andr1 == r2for two equal records both printedTrue.- Same names, same types, same order means same type, within one assembly. Swapping the order (
c) produced a different generic class. The real name,<>f__AnonymousType02`, is something you cannot write in C#. ToString()prints every property, which is handy in logs.
Projection initializers also work: new { user.Id, user.Name } takes the property names from the expressions, which is what most LINQ Select calls rely on.
with on anonymous types (C# 10)
Since C# 10, a with expression copies an anonymous object and changes some of its properties. The original is untouched:
var d = a with { Name = "Jun" };
Console.WriteLine($"{d} original: {a}");
{ Id = 7, Name = Jun } original: { Id = 7, Name = Mina }
We confirmed the version claim by compiling the same file with LangVersion set to 9:
Program.cs(3,9): error CS8773: Feature 'with on anonymous types' is not available in C# 9.0. Please use language version 10.0 or greater.
So "read-only" is still true, and with does not break it: it creates a new instance.
When to use a tuple or a record instead
An anonymous type cannot appear in a method signature. You can return it as object or dynamic, but then you lose all compile-time checking. That is the boundary: once the data leaves the method that created it, switch to a tuple or a record.
| Need | Anonymous type | Named tuple | record / record struct |
|---|---|---|---|
| Use across method boundaries | no | yes | yes |
== compares values |
no (reference) | yes | yes |
Equals/GetHashCode by value |
yes | yes | yes |
| Property names survive reflection / JSON | yes | no (Item1, Item2) |
yes |
| Allocation per instance | heap object | none (struct) | class: heap / struct: none |
| Non-destructive copy | with (C# 10) |
with |
with |
The JSON row matters in practice. We serialized the same data three ways with System.Text.Json:
new { Id = 7, Name = "Mina" } → {"Id":7,"Name":"Mina"}
(Id: 7, Name: "Mina") → {}
(Id: 7, Name: "Mina"), IncludeFields = true → {"Item1":7,"Item2":"Mina"}
A tuple's element names exist only at compile time, and its elements are fields, which the serializer skips by default. An API that returns a tuple silently sends {}.
Allocation also matters in hot paths. We ran the same LINQ pipeline over 1,000,000 integers three ways: Select to an anonymous type, to a named tuple, and to a readonly record struct, followed by Where and Sum. Method: .NET 10.0.14, Release build, Stopwatch and GC.GetAllocatedBytesForCurrentThread, one warm-up pass then three timed runs, Apple Silicon Mac (M1 Pro).
| Projection | Run 1 | Run 2 | Run 3 | Allocated |
|---|---|---|---|---|
new { Id, Sq } |
25.1 ms | 26.1 ms | 24.8 ms | 24.0 MB |
(Id, Sq) tuple |
5.2 ms | 5.3 ms | 5.0 ms | < 1 KB |
readonly record struct |
5.9 ms | 6.1 ms | 6.0 ms | < 1 KB |
Each anonymous object cost 24 bytes on the heap (24 MB / 1 million), and the pipeline was about five times slower than with a struct. For a query that runs once over a few hundred rows this does not matter, and the anonymous type reads best. For a loop that runs per frame or per request over large arrays, a tuple or a record struct is the better default. LINQ itself is covered in C# LINQ basics.
Anonymous methods: the 2021 example and what is still unique
An anonymous method is a method body without a name, written with the delegate keyword and stored in a delegate variable. After renaming main to Main, the 2021 example printed 33:
delegate int MyDelegate(int a, int b);
// in Main:
MyDelegate add = delegate (int a, int b) { return a + b; };
Console.WriteLine(add(11, 22)); // 33
With the lowercase main the build reported error CS5001: Program does not contain a static 'Main' method suitable for an entry point. C# is case-sensitive, and main is just an ordinary method.
Since C# 3, lambdas cover almost everything an anonymous method does, in less code. One feature remains unique: an anonymous method can omit its parameter list, and it then converts to any delegate type, whatever its parameters:
EventHandler h = delegate { Console.WriteLine("clicked"); };
Func<int, int, int> zero = delegate { return 0; };
Action<string, int, DateTime> ignore = delegate { };
h(null, EventArgs.Empty); // clicked
Console.WriteLine(zero(3, 4)); // 0
A lambda always needs its parameters, though C# 9 discards ((_, _) => 0) come close. Two limits we hit with the parameterless form:
error CS8917: The delegate type could not be inferred.
error CS1688: Cannot convert anonymous method block without a parameter list to delegate type 'TryParser' because it has one or more out parameters
The first comes from var g = delegate { return 1; };: with no parameter list there is nothing to infer a type from. With an explicit list, var f = delegate (int x) { return x * 2; }; compiled and got the natural type System.Func<int, int>. The second comes from a delegate with an out parameter, which the method body would have to assign.
The C# 9 additions also apply to anonymous methods: static delegate (int x) { return x + 1; } (a static anonymous function, which cannot capture) and delegate (int _, int _) { return 1; } both compiled and ran. Lambdas, however, gained features that anonymous methods never got: expression bodies (delegate (int x) => x is a syntax error), attributes ([Obsolete] delegate (…) fails with error CS1002, the lambda version compiles), and explicit return types.
A classic use of the parameterless form is the empty-handler initializer, public event EventHandler Changed = delegate { };, which means the event is never null. It works, but every raise then calls one extra empty method, and with nullable reference types the cleaner modern form is event EventHandler? Changed; raised with Changed?.Invoke(this, e). The trade-offs of that pattern are discussed in C# events beyond the basics.
Quick rules
| Situation | Use |
|---|---|
| Shape data inside one LINQ query | anonymous type |
Composite key for GroupBy/Distinct in one method |
anonymous type (value Equals) |
Compare two values with == |
tuple or record, not an anonymous type |
| Return several values from a private helper | named tuple |
| Public API, DTO, anything serialized by name | record / record struct |
| Hot loop over large arrays | tuple or record struct (no heap allocation) |
| Handler that ignores all its arguments | delegate { … } or (_, _) => … |
| Anything else that takes a function | a lambda |
Tested with .NET SDK 10.0.403 / runtime 10.0.14, C# 14, macOS on Apple Silicon (M1 Pro).
FAQ
Are two anonymous objects with the same values equal?
Equals says yes, == says no. For two new { Id = 7, Name = "Mina" } objects, a.Equals(b) printed True and a == b printed False, because anonymous types override Equals and GetHashCode but not the == operator, which stays a reference comparison. Records and tuples compare by value with == as well.
Can I change a property of an anonymous type?
No. Every property is read-only; a.Id = 8; fails with error CS0200. Since C# 10 you can make a modified copy with a with expression: var d = a with { Name = "Jun" };. With LangVersion 9 that line fails with error CS8773.
Is there anything an anonymous method can do that a lambda cannot?
One thing: drop the parameter list entirely. delegate { ... } converts to any delegate type whose parameters it ignores, such as EventHandler or Func<int, int, int>. A lambda must list its parameters, although (_, _) => discards come close. In the other direction, lambdas can do several things anonymous methods cannot, such as expression bodies, attributes and a natural type when written as delegate { }.