Intro To C# Tuples

I recently had to write interview questions specifically for features that made it into C# 7. You can have a quick refresher on what made it in here : https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7. A scan down the list reveals some interesting stuff (out variables, pattern matching, throw expressions, discards), and also some stuff that I honestly don’t seem to ever use (local functions – I honestly don’t see the use of these…). But something that sits right in the middle is Tuples. It’s a language feature that seems pretty powerful… But I rarely use dynamic or anonymous objects, preferring strongly typed classes in 99.999% of cases. For “business” development, things like this rarely override the need for the code that is extremely maintainable.  And yet, Tuples drew me in….

So here we have it. A quick crash course in Tuples (aka, What I learned about Tuples in the past week).

Tuples Before C# 7

Tuples were actually in .NET Framework before C# 7. But they were complete crap and were more of a framework construct rather than being embedded in the language.

For example, you could do something like so :

Tuple<string, int> GetData()
{
    return new Tuple<string, int>("abc", 123);
    //Alternatively. 
    return Tuple.Create("abc", 123);
}

void DoWork()
{
    Console.WriteLine(GetData().Item1);
}

But the fact you had Item1 and Item2 honestly wasn’t very appealing in most use cases. The fact you had to have the Tuple object be the return type as well made it feel like it was hardly more appealing than just returning a list, dictionary, or some other data structure that is simply a “list” of items.

Ontop of that, there was this weird (Atleast I thought so initially) limit of 8 items within the tuple. For example if I passed in 9 parameters into my Tuple.Create statement :

var myBigTuple = Tuple.Create("abc", 123, 123, 123, 123, 123, 123, 123, 123);

Things blow up. Why? Well when we decompile the .NET code :

public static Tuple<T1> Create<[NullableAttribute(2)] T1>(T1 item1);
public static Tuple<T1, T2> Create<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2>(T1 item1, T2 item2);
public static Tuple<T1, T2, T3> Create<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3>(T1 item1, T2 item2, T3 item3);
public static Tuple<T1, T2, T3, T4> Create<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3, [NullableAttribute(2)] T4>(T1 item1, T2 item2, T3 item3, T4 item4);
public static Tuple<T1, T2, T3, T4, T5> Create<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3, [NullableAttribute(2)] T4, [NullableAttribute(2)] T5>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5);
public static Tuple<T1, T2, T3, T4, T5, T6> Create<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3, [NullableAttribute(2)] T4, [NullableAttribute(2)] T5, [NullableAttribute(2)] T6>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6);
public static Tuple<T1, T2, T3, T4, T5, T6, T7> Create<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3, [NullableAttribute(2)] T4, [NullableAttribute(2)] T5, [NullableAttribute(2)] T6, [NullableAttribute(2)] T7>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7);
public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>> Create<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3, [NullableAttribute(2)] T4, [NullableAttribute(2)] T5, [NullableAttribute(2)] T6, [NullableAttribute(2)] T7, [NullableAttribute(2)] T8>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8);

So basically each create statement has been manually added up to 8. And on that 8th statement you can actually add a Tuple as the final param to have a nested Tuple. Pretty nasty stuff.

Leave a comment if you used Tuples in this initial framework version and what the use case was, because I am genuinely curious who used them like this.

ValueTuples vs Tuple Class

So you might have heard the name “ValueTuple” thrown about, but what is that exactly?

In .NET 4.0 (e.g. The old Tuple), we had a Tuple Class. It basically acts like any other C# class. However in C# 7, The ValueTuple struct type was added. The main difference being that the Tuple class, being a class, is a reference type while Value Tuple (Which we will talk about below) is a value type (struct).

Named Tuples

So the first change in C# 7 is that you can now used named tuples. So we do away with that whole Item1, Item2 business. You can now name the properties inside the tuple by either naming it on the left hand side like :

void GetData()
{
    (string stringValue, int numberValue) myTuple = ("abc", 123);
    var myNumber = myTuple.numberValue;
}

Or naming it on the right hand side like :

void GetData()
{
    var myTuple = (stringValue: "abc", numberValue :123);
    var myNumber = myTuple.numberValue;
}

This does away with the whole “Item1” business which was one of my main complaints about earlier versions of tuples. It *almost* means that if you are calling a method that returns a named tuple, your calling code can act as if it’s not a tuple at all and is instead a class. Almost. Looking for the best promo code casino offers? Check out our extensive collection of promotions and bonuses to maximize your gaming experience at various online casinos!

Returning Tuples From Methods

So before we had to have the return type of “Tuple”. Well it’s a slightly different syntax now that allows us to also name the tuple on the way out. It works a bit like so :

(string stringValue, int numberValue) GetData()
{
    return ("abc", 123);
}

void DoWork()
{
    var myData = GetData();
    Console.WriteLine(myData.stringValue);
}

Note that even if the method you are calling does not name the properties of the tuple, you can chose to do so yourself like so :

(string, int) GetData()
{
    return ("abc", 123);
}

void DoWork()
{
    (string stringValue, int numberValue) myData = GetData();
    Console.WriteLine(myData.stringValue);
}

Pretty easy stuff.

Deconstruction

There’s now this concept of “deconstruction” where we can take a tuple and turn it into individual variables. So for example :

(string, int) GetData()
{
    return ("abc", 123);
}

void DoWork()
{
    var (stringValue, numberValue) = GetData();
    Console.WriteLine(stringValue);
}

This can be useful if you are calling a library that returns a tuple but you ain’t about that and want individual variables.

I can also see this being pretty useful instead of using out params. For example, imagine if int.TryParse was reworked to look like :

(int, bool) ParseInt(string input)
{
    try
    {
        return (int.Parse(input), true);
    }
    catch
    {
        return (0, false);
    }
}

void DoWork()
{
    var (output, succeeded) = ParseInt("123");
    Console.WriteLine(succeeded);
}

I mean maybe not that much of an improvement, but we can see how it could be useful if you don’t want to use Tuples at all but are forced to calling a method that does use them.

Tuples Are Sometimes Immutable

A quick note on the immutability on Tuples. Are Tuples immutable? The answer is… It depends. C# Tuples *are not* immutable, but .NET Framework Tuples (The old way) are. For example :

static (string, int) GetData()
{
    return ("abc", 123);
}

static Tuple<string, int> GetDataOld()
{
    return Tuple.Create("abc", 123);
}

static void DoWork()
{
    (string stringValue, int numberValue) myData = GetData();
    myData.stringValue = "def"; //Fine

    var myDataOld = GetDataOld();
    myDataOld.Item1 = "def"; //Error

    Console.WriteLine(myData.stringValue);
}

If we use the new construct of Tuples (Named or Unamed), you are able to set values on the resulting tuple object. In the .NET construct where you use Tuple.Create, you cannot set the value of an item. This is probably very rarely going to come up as we go forward since it’s unlikely you will use the old Tuple construct, but it’s something to be aware of.

Alternatives To Tuples

Let’s look at some alternatives to how we might return the same data from a method. I’m going to use our example of parsing an int earlier as it’s actually a good example of returning two pieces of data from a single method.

Tuple

(int, bool) ParseInt(string input)
{
    try
    {
        return (int.Parse(input), true);
    }
    catch
    {
        return (0, false);
    }
}

void DoWork()
{
    var (output, succeeded) = ParseInt("123");
    Console.WriteLine(succeeded);
}

Out Param

bool ParseInt(string input, out int result)
{
    try
    {
        result = int.Parse(input);
        return true;
    }
    catch
    {
        result = 0;
        return false;
    }
}

void DoWork()
{
    var succeeded = ParseInt("123", out int result);
    Console.WriteLine(succeeded);
}

Dynamic Object

dynamic ParseInt(string input)
{
    IDictionary<string, object> returnObject = new ExpandoObject();
    try
    {
        returnObject.Add("result", int.Parse(input));
        returnObject.Add("succeeded", true);
    }
    catch
    {
        returnObject.Add("result", 0);
        returnObject.Add("succeeded", false);
    }

    return returnObject;
}

void DoWork()
{
    var result = ParseInt("123");
    Console.WriteLine(result.succeeded);
}

Return Object

class ParseIntResult
{
    public bool Succeeded { get; set; }
    public int Result { get; set; }
}

ParseIntResult ParseInt(string input)
{
    try
    {
        return new ParseIntResult { Result = int.Parse(input), Succeeded = true };
    }
    catch
    {
        return new ParseIntResult { Result = 0, Succeeded = false };
    }
}

void DoWork()
{
    var result = ParseInt("123");
    Console.WriteLine(result.Succeeded);
}

Which one looks best do you? We can remove the dynamic object because IMO that’s easily the worse. Out param is probably the next worst.  In terms of readability and maintainability, the return object/class looks the easiest but Tuple honestly isn’t that bad. So that leads us to….

So When Should You Use Tuples?

Happy for this to lead to comments saying I’m wrong. But in most cases, the usage of a Tuple is going to be wrong. I used the example of parsing an integer above because I think it’s one of the few cases where it might be reasonable to use. If you are creating a utility library and those library methods need to return more than one result but the creation of a return object for each method is too much, then a Tuple *could* be used (But doesn’t mean it should).

In almost all cases, I would start with a return class, and only then if that doesn’t look right, try a Tuple. But adding a Tuple to start with in most cases is going to be a bad move.

3 thoughts on “Intro To C# Tuples”

  1. Personally I prefer the out method when I have a conditional result. It has the same kind if clarity as pattern matching. Of course this also requires C#7 for in-line out variables.

    if (int.TryParse(myStr), out int myInt)
    {
    	// can be parsed...
    }
    
    Reply
  2. If we declared

    (int, bool) ParseInt(string input)

    , as you suggested, we could use pattern matching to test:

        if (ParseInt("123") is (var i, true)) {
            Console.WriteLine(i);
        }
    

    I.e., we deconstruct the result tuple into the variable `i` and test whether the second item is `true` at the same time. This would look nicer than the `out` parameter in `TryParse`.

    Reply

Leave a Comment