Is Using .NET 4.0 Tuples in my C# Code a Poor Design Decision?

41,727

Solution 1

Tuples are great if you control both creating and using them - you can maintain context, which is essential to understanding them.

On a public API, however, they are less effective. The consumer (not you) has to either guess or look up documentation, especially for things like Tuple<int, int>.

I would use them for private/internal members, but use result classes for public/protected members.

This answer also has some info.

Solution 2

The way I see it, a Tuple is a shortcut to writing a result class (I am sure there are other uses too).

There are indeed other valuable uses for Tuple<> - most of them involve abstracting away the semantics of a particular group of types that share a similar structure, and treating them simply as ordered set of values. In all cases, a benefit of tuples is that they avoid cluttering your namespace with data-only classes that expose properties but not methods.

Here's an example of a reasonable use for Tuple<>:

var opponents = new Tuple<Player,Player>( playerBob, playerSam );

In the above example we want to represent a pair of opponents, a tuple is a convenient way of pairing these instances without having to create a new class. Here's another example:

var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );

A poker hand can be thought of as just a set of cards - and tuple (may be) a reasonable way of expressing that concept.

setting aside the possibility that I am missing the point of Tuples, is the example with a Tuple a bad design choice?

Returning strongly typed Tuple<> instances as part of a public API for a public type is rarely a good idea. As you yourself recognize, tuples requires the parties involved (library author, library user) to agree ahead of time on the purpose and interpretation of the tuple types being used. It's challenging enough to create APIs that are intuitive and clear, using Tuple<> publicly only obscures the intent and behavior of the API.

Anonymous types are also a kind of tuple - however, they are strongly typed and allow you to specify clear, informative names for the properties belonging to the type. But anonymous types are difficult to use across different methods - they were primarily added to support technologies like LINQ where projections would produce types to which we wouldn't normally want to assign names. (Yes, I know that anonymous types with the same types and named properties are consolidated by the compiler).

My rule of thumb is: if you will return it from your public interface - make it a named type.

My other rule of thumb for using tuples is: name method arguments and localc variables of type Tuple<> as clearly as possible - make the name represent the meaning of the relationships between elements of the tuple. Think of my var opponents = ... example.

Here's an example of a real-world case where I've used Tuple<> to avoid declaring a data-only type for use only within my own assembly. The situation involves the fact that when using generic dictionaries containing anonymous types, it's becomes difficult to use the TryGetValue() method to find items in the dictionary because the method requires an out parameter which cannot be named:

public static class DictionaryExt 
{
    // helper method that allows compiler to provide type inference
    // when attempting to locate optionally existent items in a dictionary
    public static Tuple<TValue,bool> Find<TKey,TValue>( 
        this IDictionary<TKey,TValue> dict, TKey keyToFind ) 
    {
        TValue foundValue = default(TValue);
        bool wasFound = dict.TryGetValue( keyToFind, out foundValue );
        return Tuple.Create( foundValue, wasFound );
    }
}

public class Program
{
    public static void Main()
    {
        var people = new[] { new { LastName = "Smith", FirstName = "Joe" },
                             new { LastName = "Sanders", FirstName = "Bob" } };

        var peopleDict = people.ToDictionary( d => d.LastName );

        // ??? foundItem <= what type would you put here?
        // peopleDict.TryGetValue( "Smith", out ??? );

        // so instead, we use our Find() extension:
        var result = peopleDict.Find( "Smith" );
        if( result.First )
        {
            Console.WriteLine( result.Second );
        }
    }
}

P.S. There is another (simpler) way of getting around the issues arising from anonymous types in dictionaries, and that is to use the var keyword to let the compiler 'infer' the type for you. Here's that version:

var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
   // use foundItem...
}

Solution 3

Tuples can be useful... but they can also be a pain later. If you have a method that returns Tuple<int,string,string,int> how do you know what those values are later. Were they ID, FirstName, LastName, Age or were they UnitNumber, Street, City, ZipCode.

Solution 4

Tuples are pretty underwhelming addition to the CLR from the perspective of a C# programmer. If you have a collection of items that varies in length, you don't need them to have unique static names at compile time.

But if you have a collection of constant length, this implies that the fixed of locations in the collection each have a specific pre-defined meaning. And it is always better to give them appropriate static names in that case, rather than having to remember the significance of Item1, Item2, etc.

Anonymous classes in C# already provide a superb solution to the most common private use of tuples, and they give meaningful names to the items, so they are actually superior in that sense. The only problem is that they can't leak out of named methods. I'd prefer to see that restriction lifted (perhaps only for private methods) than have specific support for tuples in C#:

private var GetDesserts()
{
    return _icecreams.Select(
        i => new { icecream = i, topping = new Topping(i) }
    );
}

public void Eat()
{
    foreach (var dessert in GetDesserts())
    {
        dessert.icecream.AddTopping(dessert.topping);
        dessert.Eat();
    }
}

Solution 5

Similar to keyword var, it is intended as a convenience - but is as easily abused.

In my most humble opinion, do not expose Tuple as a return class. Use it privately, if a service or component's data structure requires it, but return well-formed well-known classes from public methods.

// one possible use of tuple within a private context. would never
// return an opaque non-descript instance as a result, but useful
// when scope is known [ie private] and implementation intimacy is
// expected
public class WorkflowHost
{
    // a map of uri's to a workflow service definition 
    // and workflow service instance. By convention, first
    // element of tuple is definition, second element is
    // instance
    private Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> _map = 
        new Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> ();
}
Share:
41,727

Related videos on Youtube

Jason Webb
Author by

Jason Webb

I am a programmer in Utah. I spend my day coding Scala and Java, but also enjoy Clojure, Python, Ruby, Javascript and C. I occasionally blog on my website http://www.bigjason.com/blog/.

Updated on June 01, 2020

Comments

  • Jason Webb
    Jason Webb almost 4 years

    With the addition of the Tuple class in .net 4, I have been trying to decide if using them in my design is a bad choice or not. The way I see it, a Tuple can be a shortcut to writing a result class (I am sure there are other uses too).

    So this:

    public class ResultType
    {
        public string StringValue { get; set; }
        public int IntValue { get; set; }
    }
    
    public ResultType GetAClassedValue()
    {
        //..Do Some Stuff
        ResultType result = new ResultType { StringValue = "A String", IntValue = 2 };
        return result;
    }
    

    Is equivalent to this:

    public Tuple<string, int> GetATupledValue()
    {
        //...Do Some stuff
        Tuple<string, int> result = new Tuple<string, int>("A String", 2);
        return result;
    }
    

    So setting aside the possibility that I am missing the point of Tuples, is the example with a Tuple a bad design choice? To me it seems like less clutter, but not as self documenting and clean. Meaning that with the type ResultType, it is very clear later on what each part of the class means but you have extra code to maintain. With the Tuple<string, int> you will need to look up and figure out what each Item represents, but you write and maintain less code.

    Any experience you have had with this choice would be greatly appreciated.

    • FrustratedWithFormsDesigner
      FrustratedWithFormsDesigner almost 14 years
      Tuples are great if you like LISP!
    • BoltBait
      BoltBait almost 14 years
      I've never understood why we can't say "row" instead of "tuple". Why do we need a new word for this?
    • Raquel
      Raquel almost 14 years
      @Boltbait: because tuple is from set theory en.wikipedia.org/wiki/Tuple
    • FrustratedWithFormsDesigner
      FrustratedWithFormsDesigner almost 14 years
      @BoltBait: A "tuple" is an ordered set of values, and doesn't necessarily have anything to do with rows in a database.
    • Justin
      Justin almost 14 years
      Tuples in c# would be even nicer if there was some good tuple unpacking action :)
    • svick
      svick almost 14 years
      @BoltBait The way I see it is that when you have a row, you alse have a table and columns. But in the case of Tuple, you don't have that. They also aren't the same, because row's members have names.
    • Dan Bryant
      Dan Bryant almost 14 years
      My understanding is that Tuples are primarily intended for interoperability with dynamic languages. Within purely C# code, it's probably cleaner to use explicitly defined classes (when exposed publicly) or anonymous types.
    • Seth
      Seth almost 14 years
      @Matthew - I suppose that's true. C# without { would be like lisp without ( :D
    • John Saunders
      John Saunders almost 14 years
      @Jason: your question asks about tuples in .NET 4.0, yet the title asks about tuples in C#. Why is that? Tuples have nothing at all to do with C#. They are purely a .NET Framework thing.
    • Jason Webb
      Jason Webb almost 14 years
      @John Saunders My question is specifically about design decisions in c#. I don't use any other .net languages so I am unsure of how tuples impact them. So I marked it c#. It seemed reasonable to me but the change to .net is fine too I guess.
    • Ukko
      Ukko almost 14 years
      @FrustratedWithFormsDesigner: Lisp really does not have tuples, just lists. You cannot add an element to a tuple like you can a list and tuples also carry more type information. For instance I can have a tuple of (int, int, String) which is a different type than a list of strings and ints.
    • John Saunders
      John Saunders almost 14 years
      @Jason: C# does not have tuples at all. There is no tuple support in the C# programming language. For instance, you can't do return (1,2);. It's important to understand the distinction between the .NET Framework and a .NET programming language like C# or VB.NET.
    • Jason Webb
      Jason Webb almost 14 years
      @John I do get that. What I am saying is I was looking for answers having to do specifically with c# and the use of the .net 4 Tuple class. The answer can be different in F# for instance since F# has better support for tuples. But it doesn't matter. Your edit to my title is fine. Good and helpful answers are coming in.
    • FrustratedWithFormsDesigner
      FrustratedWithFormsDesigner almost 14 years
      @Ukko: Hmm that's a good point, though the last time I worked with tuples (nested tuples, at that) - though it was with a Tuples class someone has written in Java (so maybe not quite the same as .NET) - I had a sudden LISP flashback. But I guess it's a more superficial similarity.
    • John Saunders
      John Saunders almost 14 years
      @Jason: I'm glad you're getting good answers, but what has me wondering is the fact that there are no design decisions in C# with respect to tuples, beyond the decision not to add language support for tuples.
    • Brett Widmeier
      Brett Widmeier almost 14 years
      @John Saunders: I am pretty sure he is asking about design decisions relating to using or not using Tuples in an application that is written purely in C#. I do not believe he is asking about design decisions that went into making the C# language.
    • John Saunders
      John Saunders almost 14 years
      @Brett: if your interpretation is correct, then the title was worse than I thought.
    • granadaCoder
      granadaCoder about 4 years
      #anotherHolyWar Food for thought : pedrorijo.com/blog/tuples-are-evil Why are tuples so bad? The bigger problems of using tuples: Usually, you can’t easily understand what each tuple field means without getting a lot of context. This probably includes trace back until the moment the tuple was created. Instead of using a tuple, had you chosen for a case class with proper names, it would be really straightforward to understand the meaning of each field. If you want to evolve the tuple to hold more info (meaning, adding a new value to the tuple), you break code (COMMENT LIMIT MET)
  • Clinton Pierce
    Clinton Pierce almost 14 years
    The difference between public/private is really important, thanks for bringing it up. Returning tuples in your public API is a really bad idea, but internally where things might be tightly coupled (but that's okay) it's fine.
  • stakx - no longer contributing
    stakx - no longer contributing almost 14 years
    Nice answer. But I think it would be more reasonable to use a Card[] array in your pokerHand example.
  • LBushkin
    LBushkin almost 14 years
    @stakx: Yes, I agree. But keep in mind - the example is largely intended to illustrate cases when the use of a Tuple<> could make sense. There are always alternatives...
  • Matt Greer
    Matt Greer almost 14 years
    Mainstream "RAD" languages (Java is the best example) have always chosen safety and avoiding shooting yourself in the foot type scenarios. I am glad Microsoft is taking some chances with C# and giving the language some nice power, even if it can be misused.
  • Chris Marisic
    Chris Marisic almost 14 years
    The var keyword is not possible to abuse. Perhaps you meant dynamic?
  • johnny g
    johnny g almost 14 years
    @Chris Marisic, just to clarify i did not mean in the same scenario - you are absolutely correct, var cannot be passed back or exist outside of its declared scope. however, it is still possible to use it in excess of its intended purpose and be "abused"
  • Joel Mueller
    Joel Mueller almost 14 years
    I think that replacing out parameters with a Tuple, as in TryGetValue or TryParse, is one of the few cases where it might make sense to have a public API return a Tuple. In fact, at least one .NET language automatically makes this API change for you.
  • johnny g
    johnny g almost 14 years
    @Chris Marisic, also, did not mean to single out var. indeed, any feature can be "abused" to obtain a "desired" result.
  • Jason Webb
    Jason Webb almost 14 years
    How are tuples a "language feature"? Its a class available to all .net languages isn't it?
  • Joel Mueller
    Joel Mueller almost 14 years
    Relatively useless to C#, perhaps. But System.Tuple wasn't added to the framework because of C#. It was added to ease interoperability between F# and other languages.
  • Philippe Leybaert
    Philippe Leybaert almost 14 years
    @Jason: I didn't say it was a language feature (I did mistype that, but I corrected it before you made this comment).
  • Joel Mueller
    Joel Mueller almost 14 years
    @Jason - languages with direct support for tuples have implicit support for deconstructing tuples into their component values. Code written in those languages typically never, ever, accesses the Item1, Item2 properties. let success, value = MethodThatReturnsATuple()
  • Philippe Leybaert
    Philippe Leybaert almost 14 years
    @Joel: this question is tagged as C#, so it's somehow about C#. I still think they could have turned it into a first-class feature in the language.
  • Jason Webb
    Jason Webb almost 14 years
    @Philippe Leybaert That makes more sense. I retract my earlier comment. I must have been looking at a cached version of the page before your edit.
  • Jason Webb
    Jason Webb almost 14 years
    @Joel Mueller It was about c# specifically. John Saunders edited the title.
  • Joel Mueller
    Joel Mueller almost 14 years
    @Philippe - I agree, having first-class support for tuples in C# would be very convenient. But the fact remains that tuples are not useless, even to C#. Without System.Tuple, C# code that called into an F# library that returns a tuple from a method would have had to add a reference to FSharp.Core.dll. Because tuples are part of the framework instead of part of F#, it makes interoperability easier.
  • Chris Marisic
    Chris Marisic almost 14 years
    I still stand on that var cannot be abused period. It is merely a keyword to make a shorter variable definition. Your argument perhaps is about anonymous types but even those really can't be abused because they're still normal statically linked types now dynamic is quite a different story.
  • Matt Greer
    Matt Greer almost 14 years
    var can be abused in the sense that if it is overused it can sometimes cause issues for the person reading the code. Especially if you're not in Visual Studio and lack intellisense.
  • Ashwin
    Ashwin almost 14 years
    @stakx: Tuples are also immutable, whereas arrays are not.
  • Kugel
    Kugel about 13 years
    You can use Tuple.Create instead of constructor. It's shorter.
  • Gennady Vanin Геннадий Ванин
    Gennady Vanin Геннадий Ванин about 11 years
    I understood that Tuples are useful only as immutable poker player objects.
  • supercat
    supercat about 11 years
    As a general feature, what I'd like to see would be for .net to define a standard for a few special kinds of anonymous types, such that e.g. struct var SimpleStruct {int x, int y;} would define a struct with a name that starts with a guid associated with simple structs and has something like {System.Int16 x}{System.Int16 y} appended; any name starting with that GUID would be required to represent a struct containing just those elements and particular specified contents; if multiple definitions for the same name are in scope and they match, all would be considered the same type.
  • supercat
    supercat about 11 years
    Such a thing would be helpful for a few kinds of types, including mutable and immutable class- and struct- tuples as well as interfaces and delegates for use in situations where matching structure should be deemed to imply matching semantics. While there are certainly reasons why it's may be useful to have two delegate types that have the same parameters not be considered interchangeable, it would also be helpful if one could specify that a method wants a delegate that takes some combination of parameters but beyond that doesn't care what kind of delegate it is.
  • supercat
    supercat about 11 years
    It's unfortunate that if two different people want to write functions that take as input delegates which need a single ref parameter, the only way it will be possible to have one delegate that can be passed to both functions will be if the one person produces and compiles a delegate type and gives it to the other to build against. There's no way both people can indicate that the two types should be considered synonymous.
  • contactmatt
    contactmatt about 11 years
    Tightly coupled, or tightly tupled? ;)
  • Admin
    Admin over 10 years
    One small additional advantage: you can safely reorder the parameters for a class. Doing so for a tuple will break code, and if the fields have similar types this may not be detectable at compile time.
  • Admin
    Admin over 10 years
    Do you have any more concrete examples of where you'd use anonymous classes rather than Tuple? I just skimmed through 50 places in my codebase where I'm using tuples, and all are either return values (typically yield return) or are elements of collections which are used elsewhere, so no go for anonymous classes.
  • Daniel Earwicker
    Daniel Earwicker over 10 years
    @JonofAllTrades - the consuming code of those collections/enumerables therefore have references to Item1, Item2, etc. scattered around them. Wouldn't they be more readable if they referred to meaningful names?
  • Admin
    Admin over 10 years
    @DanielEarwicker: Absolutely, which is why I'd like to refactor them. However, since I can't return an anonymous class, or declare (for example) a Dictionary<> with an anonymous class for the key, I don't think they're an option for any of these instances. I could write and use little structs, but not anonymous classes.
  • Daniel Earwicker
    Daniel Earwicker over 10 years
    @JonofAllTrades - yes, little classes. Anonymous classes are sadly of no use in public APIs (half my above answer was pleading for a new language feature, for exactly that reason!) They are occasionally useful in complex Linq expressions (although there you can typically use let to achieve the same thing in a more readable way).
  • Admin
    Admin over 10 years
    @DanielEarwicker: Gotcha, so people's references here to "public APIs" uses "public" broadly, meaning about any time you expose information outside of a method (as I am for most of these 50 cases). I had interpreted it as meaning "don't use tuples for a published API which will be used by other developers", but I see that this is too narrow. Thank you for the clarification!
  • Daniel Earwicker
    Daniel Earwicker over 10 years
    @JonofAllTrades - opinions probably vary on that, but I try to design the public methods as if they were going to be used by someone else, even if that someone is me, so I'm giving myself good customer service! You tend to call a function more times than you write it. :)
  • Dave Cousineau
    Dave Cousineau almost 10 years
    an anonymous type will be much more readable; as would using real variable names instead of 'x'
  • chiccodoro
    chiccodoro over 9 years
    +1 Great answer - I love your two initial examples which actually changed my mind slightly. I never saw examples before where a tuple was at least as much appropriate as a custom struct or type. However, your last "real life example" to me seems not to add much value any more. The first two examples are perfect and simple. The last is a bit hard to understand and your "P.S." kind of renders it useless since your alternate approach is much simpler and does not require tuples.
  • Kat
    Kat over 9 years
    Couldn't appropriate documentation be useful here? For example, Scala's StringOps (basically a class of "extension methods" for Java's String) has a method parition(Char => Boolean): (String, String) (takes in predicate, returns tuple of 2 strings). It's obvious what the output is (obviously one is going to be the characters for which the predicate was true and the other for which it was false, but it's not clear which is which). It's the documentation that clears this up (and pattern matching can be used to make it clear in usage which is which).
  • Bryan Watts
    Bryan Watts over 9 years
    @Mike: That makes it hard to get right and easy to get wrong, the hallmark of an API that will cause pain for someone somewhere :-)
  • stonedauwg
    stonedauwg about 9 years
    Was trying to understand your example but using .NET 4/4.5 I can't get it to compile. It complains that result.First and result.Second are not members of Tuple. Did you mean Item1, Item2 perhaps?
  • Clinton Pierce
    Clinton Pierce almost 9 years
    It would now. That's a SO problem, answers posted years and years ago never get cleaned up or updated for new technology.
  • mike gold
    mike gold about 8 years
    I agree, Tuples are used when a developer lacks the energy to think of a meaningful class name grouping meaningful property names. Programming is about communication. Tuples are an antipattern to communication. I would have preferred Microsoft left it out of the language to force developers to make good design decisions.
  • Neil Laslett
    Neil Laslett over 7 years
    FWIW, C# 7.0 tuples will support named members.
  • Mr. TA
    Mr. TA over 5 years
    I mentioned named tuples - ValueTuple - and I don't like them, either. First, the naming isn't strong, it's more of a suggestion, very easy to mess up because it can be assigned implicitly to either Tuple or ValueTuple with the same number and types of items. Second, the lack of inheritance and the need to copy paste the whole definition from place to place are still detriments.
  • Mike Christiansen
    Mike Christiansen over 5 years
    I agree with those points. I try to only use Tuples when I control the producer and consumer, and if they aren't too "far away". Meaning, producer and consumer in the same method = 'close', whereas producer and consumer in different assemblies = 'light years away'. So, if I create tuples in the beginning of a method, and use them at the end? Sure, I'm okay with that. I also document the terms and such. But yeah, I agree that generally they're not the right choice.
  • NotAPro
    NotAPro about 2 years
    This does not answer the question