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.
I would personally never use a Tuple as a return type because there is no indication of what the values represent. Tuples have some valuable uses because unlike objects they are value types and thus understand equality. Because of this I will use them as dictionary keys if I need a multipart key or as a key for a GroupBy clause if I want to group by multiple variables and don't want nested groupings (Who ever wants nested groupings?). To overcome the issue with extreme verbosity you can create them with a helper method. Keep in mind if you are frequently accessing members (through Item1, Item2, etc) then you should probably use a different construct such as a struct or an anonymous class.
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 calledItem1
andItem2
). This is even more important if the types of the two fields are the same: the name clearly distinguishes between them.IMO these "tuples" are basically all public access anonymous
struct
types with unnamed members.The only place I would use tuple is when you need to quickly blob together some data, in a very limited scope. The semantics of the data should be are obvious, so the code is not hard to read. So using a tuple (
int
,int
) for (row,col) seems reasonable. But I'm hard pressed to find an advantage over astruct
with named members (so no mistakes are made and row/column aren't accidentally interchanged)If you're sending data back to the caller, or accepting data from a caller, you really should be using a
struct
with named members.Take a simple example:
The tuple version
I don't see any advantage to using tuple in the place of a struct with named members. Using unnamed members is a step backward for the readability and understandability of your code.
Tuple strikes me as a lazy way to avoid creating a
struct
with actual named members. Overuse of tuple, where you really feel you/or someone else encountering your code would need named members is A Bad Thing™ if I ever saw one.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#:
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<>
: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:
A poker hand can be thought of as just a set of cards - and tuple (may be) a reasonable way of expressing that concept.
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, usingTuple<>
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 myvar 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 theTryGetValue()
method to find items in the dictionary because the method requires anout
parameter which cannot be named: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: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:
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.