In the evolving landscape of C# programming, efficiency and readability are key. As developers, we constantly seek ways to write cleaner, more efficient code. One such avenue is the use of value tuples, a feature that, while not new, remains underutilized in many C# projects. This post aims to shed light on value tuples and how they can be a more efficient alternative to anonymous objects, especially in scenarios where data structure simplicity and performance are paramount.

Anonymous objects have been a staple in C# for creating quick, one-off data structures, but they come with limitations, especially in terms of performance and type safety. Enter value tuples, a feature introduced in C# 7.0, offering a lightweight way to handle data structures without the need for custom classes or anonymous types. In this post, we’ll explore what value tuples are, how they compare to anonymous objects, and why they might be the better choice in your next C# project.

What are Anonymous Objects?

Anonymous objects in C#, introduced in version 3.0, are a convenient way to encapsulate a set of read-only properties into a single object without explicitly defining a type. They are often used in LINQ queries or when a method needs to return multiple values without creating a specific class or struct. For example:

var anonymousObj = new { Name = "John Doe", Age = 30 };

This creates an object with Name and Age properties. However, despite their convenience, anonymous objects have their downsides. They are reference types, which means they are allocated on the heap and can have a higher memory footprint. Moreover, they are not ideal for public APIs and cross-method data transport due to their inherent type ambiguity.

Introduction to Value Tuples

Value tuples, on the other hand, are a value type introduced in C# 7.0. They provide a straightforward way to have lightweight, temporary data structures. Unlike anonymous objects, value tuples are struct-based, making them more efficient in terms of memory usage, as they are typically allocated on the stack. Here’s a simple value tuple declaration:

(string Name, int Age) personTuple = ("John Doe", 30);

The advantages of value tuples include improved performance and the ability to pass them around methods and return them from functions with ease. They also bring along features like deconstruction, which allows for a more readable and concise way of handling multiple return values.


Benchmark Results

[MemoryDiagnoser]
public class AnonymousObjectVsValueTuple
{
    private static readonly Movie[] Movies = new Movie[]
    {
        new Movie { Title = "The Dark Knight", Year = 2008, Director = "Christopher Nolan", Rating = 9.0 },
        new Movie { Title = "The Shawshank Redemption", Year = 1994, Director = "Frank Darabont", Rating = 9.3 },
        new Movie { Title = "Pulp Fiction", Year = 1994, Director = "Quentin Tarantino", Rating = 8.9 },
        new Movie { Title = "Inception", Year = 2010, Director = "Christopher Nolan", Rating = 8.8 },
        new Movie { Title = "Fight Club", Year = 1999, Director = "David Fincher", Rating = 8.8 },
        new Movie { Title = "The Matrix", Year = 1999, Director = "The Wachowski Brothers", Rating = 8.7 },
        new Movie { Title = "Goodfellas", Year = 1990, Director = "Martin Scorsese", Rating = 8.7 },
        new Movie { Title = "Se7en", Year = 1995, Director = "David Fincher", Rating = 8.6 },
        new Movie { Title = "The Silence of the Lambs", Year = 1991, Director = "Jonathan Demme", Rating = 8.6 },
        new Movie { Title = "Star Wars: Episode V - The Empire Strikes Back", Year = 1980, Director = "Irvin Kershner", Rating = 8.7 },
        new Movie { Title = "Forrest Gump", Year = 1994, Director = "Robert Zemeckis", Rating = 8.8 },
        new Movie { Title = "The Lord of the Rings: The Fellowship of the Ring", Year = 2001, Director = "Peter Jackson", Rating = 8.8 },
        new Movie { Title = "The Lord of the Rings: The Return of the King", Year = 2003, Director = "Peter Jackson", Rating = 8.9 },
        new Movie { Title = "The Godfather", Year = 1972, Director = "Francis Ford Coppola", Rating = 9.2 },
        new Movie { Title = "The Godfather: Part II", Year = 1974, Director = "Francis Ford Coppola", Rating = 9.0}
    };

    [Benchmark]
    public Dictionary<string, IGrouping<string, Movie>> AnonymousObject()
    {
        var movies = Movies.GroupBy(m => m.Director)
            .Select(g => new
            {
                Director = g.Key,
                Movies = g
            })
            .ToDictionary(g => g.Director, g => g.Movies);

        return movies;
    }

    [Benchmark]
    public Dictionary<string, IGrouping<string, Movie>> ValueTuple()
    {
        var movies = Movies.GroupBy(m => m.Director)
            .Select(g =>
            (
                Director: g.Key,
                Movies: g
            ))
            .ToDictionary(g => g.Director, g => g.Movies);

        return movies;
    }
}


public class Movie
{
    public string Title { get; set; }
    public int Year { get; set; }
    public string Director { get; set; }
    public double Rating { get; set; }
}

Running this benchmark yields these results:

MethodMeanErrorStdDevGen0Gen1Allocated
AnonymousObject1,104.7 ns20.89 ns21.45 ns0.34900.00192.85 KB
ValueTuple942.9 ns15.04 ns13.33 ns0.30710.00192.52 KB
Benchmark on .NET 8

As we can see using this trick reduces time and allocations without changing the result at all.

By embracing value tuples, C# developers can streamline their codebases, making them more efficient and expressive. As with any feature, the key is to understand when and how to use them effectively.

Leave a comment

Trending