views:

1194

answers:

9

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.

+2  A: 

Using a class like ResultType is clearer. You can give meaningful names to the fields in the class (whereas with a tuple they would be called Item1 and Item2). This is even more important if the types of the two fields are the same: the name clearly distinguishes between them.

Richard Fearn
+5  A: 

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.

Matthew Whited
+32  A: 

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.

Bryan Watts
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.
clintp
A: 

It depends, of course! As you said, a tuple can save you code and time when you want to group some items together for local consumption. You can also use them to create more generic processing algorithms than you can if you pass a concrete class around. I can't remember how many times I've wished I had something beyond KeyValuePair or a DataRow to quickly pass some date from one method to another.

On the other hand, it is quite possible to overdo it and pass around tuples where you can only guess what they contain. If you are going to use a tuple across classes, perhaps it would be better to create one concrete class.

Used in moderation of course, tuples can lead to more concise and readable code. You can look to C++, STL and Boost for examples of how Tuples are used in other languages but in the end, we will all have to experiment to find how they best fit in the .NET environment.

Panagiotis Kanavos
+4  A: 

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>> ();
}
johnny g
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.
Matt Greer
The var keyword is not possible to abuse. Perhaps you meant dynamic?
Chris Marisic
@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"
johnny g
@Chris Marisic, also, did not mean to single out `var`. indeed, any feature can be "abused" to obtain a "desired" result.
johnny g
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.
Chris Marisic
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.
Matt Greer
+23  A: 

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...
}
LBushkin
Nice answer. But I think it would be more reasonable to use a `Card[]` array in your `pokerHand` example.
stakx
@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...
LBushkin
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.
Joel Mueller
@stakx: Tuples are also immutable, whereas arrays are not.
Robert Paulson
A: 

Tuples are a useless framework feature in .NET 4. I think a great opportunity was missed with C# 4.0. I would have loved to have tuples with named members, so you could access the various fields of a tuple by name instead of Value1, Value2, etc...

It would have required a language (syntax) change, but it would have been very useful.

Philippe Leybaert
How are tuples a "language feature"? Its a class available to all .net languages isn't it?
Jason Webb
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.
Joel Mueller
@Jason: I didn't say it was a language feature (I did mistype that, but I corrected it before you made this comment).
Philippe Leybaert
@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()`
Joel Mueller
@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.
Philippe Leybaert
@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
@Joel Mueller It was about c# specifically. John Saunders edited the title.
Jason Webb
@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.
Joel Mueller
+2  A: 

How about using Tuples in a decorate-sort-undecorate pattern? (Schwartzian Transform for the Perl people). Here's a contrived example, to be sure, but Tuples seem to be a good way to handle this kind of thing:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] files = Directory.GetFiles("C:\\Windows")
                    .Select(x => new Tuple<string, string>(x, FirstLine(x)))
                    .OrderBy(x => x.Item2)
                    .Select(x => x.Item1).ToArray();
        }
        static string FirstLine(string path)
        {
            using (TextReader tr = new StreamReader(
                        File.Open(path, FileMode.Open)))
            {
                return tr.ReadLine();
            }
        }
    }
}

Now, I could have used an Object[] of two elements or in this specific example a string [] of two elements. The point being that I could have used anything as the second element in a Tuple that's used internally and is pretty easy to read.

clintp
+1  A: 

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();
    }
}
Daniel Earwicker