C# Named Tuples: Syntax and Practical Usage
c# named tuple: Learn how to declare, access, and return C# named tuples, and understand when they are a better choice than anonymous types.
C# named tuples let you return multiple values from a method without defining a dedicated class or struct. They combine the lightweight allocation behavior of tuples with human-readable field names, so callers can access values like result.FirstName instead of result.Item1. This article covers the syntax, practical usage, and the tradeoffs you should consider before using them in production code.
Declaring Named Tuples
The simplest way to create a named tuple is with a literal that includes field names:
var person = (FirstName: "John", LastName: "Doe");
Here, person is a tuple with two fields named FirstName and LastName. The compiler infers the tuple type as (string FirstName, string LastName). You can also declare the type explicitly:
(string FirstName, string LastName) person = ("John", "Doe");
Both forms produce the same runtime type, which is an instance of System.ValueTuple<string, string>. The field names are metadata that the compiler uses for source-level access; they are not stored as part of the runtime value.
Accessing Tuple Fields by Name and Position
Once you have a named tuple, you can access fields by their names:
Console.WriteLine(person.FirstName); Console.WriteLine(person.LastName);
You can also access fields positionally using Item1, Item2, and so on, but that defeats the purpose of naming. The names are checked at compile time, so a typo like person.Firstname will produce a compiler error.
Field names are not part of the tuple's runtime type. If you assign a tuple with different names to another tuple variable, the names are ignored:
(string First, string Last) other = person; // No error
This behavior is important to understand when you pass tuples across API boundaries.
Named Tuples vs Anonymous Types
Anonymous types also provide named properties, but they are class-based and have different semantics. Consider this anonymous type:
var anon = new { FirstName = "John", LastName = "Doe" };
Anonymous types are reference types, so they are allocated on the heap. They also have value equality based on property values, but they cannot be returned from a method without using dynamic or reflection. Named tuples, on the other hand, are value types and can appear in method signatures directly.
| Feature | Named Tuple | Anonymous Type |
|---|---|---|
| Type category | Value type (struct) | Reference type (class) |
| Heap allocation | No (unless boxed) | Yes |
| Returnable from method | Yes | No (without dynamic) |
| Field names | Compile-time metadata | Compile-time properties |
| Equality semantics | Structural (ValueTuple) | Value-based |
Use a named tuple when you need a lightweight, returnable container for a few related values. Use an anonymous type when you need to project data within a method and do not need to pass it elsewhere.
Returning Named Tuples from Methods
A common use case is returning multiple values from a method. Without named tuples, you might create a custom class or use out parameters. Named tuples give you a concise alternative:
public (int Id, string Name) GetUser() { // Simulate retrieval return (42, "Ada Lovelace"); }
Callers can then access the fields by name:
var user = GetUser(); Console.WriteLine($"{user.Id}: {user.Name}");
The tuple type is part of the method signature, so it is visible in IntelliSense and in generated documentation. This makes the API self-documenting compared to returning a Tuple<int, string> where fields are Item1 and Item2.
Deconstructing Named Tuples
Deconstruction is the process of splitting a tuple into individual variables. Named tuples support deconstruction just like positional tuples:
var (id, name) = GetUser();
You can also use explicit types:
(int id, string name) = GetUser();
Deconstruction is not limited to tuples; you can define Deconstruct methods on your own types, but tuples provide it out of the box. This is useful when you want to pass individual values to other methods or use them in local scope without keeping the tuple object.
One detail to note: deconstruction discards the field names. The variables id and name are just local variables with no connection to the tuple's original names. If you need the names, keep the tuple intact.
Naming Conventions and Common Pitfalls
Because tuple field names are not part of the runtime type, they can be lost or ignored in certain scenarios. For example, if you pass a named tuple to a method that accepts a ValueTuple<int, string>, the names are not preserved in the method's parameter type. The method sees Item1 and Item2. This is a common source of confusion when working with libraries that use tuples internally.
Another pitfall is naming collisions with existing members. If you have a tuple field named Item1, it will shadow the positional Item1 property, making access ambiguous. The compiler warns about this, but it is better to avoid such names.
When defining a public API, consider whether the tuple names will survive serialization or reflection. They are stored as attributes in metadata, but many serialization frameworks ignore them. If you need stable field names across process boundaries, a dedicated class or struct is often a safer choice.
Performance Considerations
Named tuples are value types, so they are allocated on the stack or inline within an object, avoiding heap allocation when used locally. This makes them cheaper than a class-based alternative for short-lived, small data containers. However, value types are copied by value, so passing a tuple to a method or returning it from a method copies the entire struct. For tuples with many fields, this copying overhead can become significant.
Boxing occurs when you assign a tuple to a reference type, such as object or an interface. This allocates on the heap and defeats the performance advantage. Avoid boxing by keeping tuples in value-type contexts, such as generic collections that do not box, like List<(int, string)>.
For most practical uses—returning two or three values from a method—the performance characteristics are excellent. If you need to pass around a large set of related values, consider a custom struct or class to avoid repeated copying.
Compatibility and Language Version Requirements
Named tuples require C# 7.0 or later and the System.ValueTuple types. These types are included in .NET Framework 4.7, .NET Core 2.0, and later versions. If you are targeting an older framework, you can install the System.ValueTuple NuGet package. The compiler support is tied to the language version, so you must ensure your project uses C# 7.0 or newer.
When working with libraries that target older frameworks, tuple names may not be preserved if the library was compiled without the ValueTuple types. In that case, you will see Item1 and Item2 instead of the intended names. This is a compatibility consideration to keep in mind when consuming third-party code.
For internal code within a single project, named tuples are a clean and efficient way to group related values. For public APIs that will be consumed by other teams or external clients, weigh the benefits of named tuples against the loss of runtime field-name enforcement and the potential for confusion when names are stripped.