Do you have problems with C# Tuple class because items are read only?

While I was working on a small Windows Form tool in C#, which should help me to save and load parameters for a command line application, I run into the problem that not the complete textbox controls should be saved, but only the name and the text content. So, I had the requirement to save only a subset of the form fields. After thinking a while, I came to the idea to use Linq in combination with the Tuple class and I did some research about Tuples at all.

In mathematics, a finite sequence of elements is called a Tuple and they rise popularity with the implementation of functional programming languages. Like in mathematics, in functional programming a certain variable has a single value at all the time. So, Tuples are immutable or in other words “read only” by design and implementations like in C# are following this design.

Beside other usage of Tuples in programming languages, it’s commonly used to return a subset of data. Think about following use case: i.e. there is a list of territory definitions with fields like ZIP Code, City, Region, Population etc. and this list must be filtered and only a subset of the territory fields should be returned.

Here a code snippet for the above mentioned class definition:

// Define a class to store data
class Territory { public int zip; public string city; public string region; };

With Linq we could pretty easy do the filtering requirement, but we would have difficulties to return only a subset of the fields. Here a sample how it could be done:

// Define a sample list of objects to work with
Territory[] myTerritories = new Territory[] {
new Territory{zip=4153,city="Reinach",region="BL"},
new Territory{zip=8304,city="Wallisellen",region="ZH"},
new Territory{zip=3018,city="Bern",region="BE"}};

// Select a complete Territory subset variant 1
var subset1 = from Territory t in myTerritories
where t.region.StartsWith("B")
select t;
// Select a complete Territory subset variant 2
var subset2 = myTerritories.Where(t => t.region.StartsWith("B"));

So, here comes the usage of Tuples. To return only a subset of the territory fields, this fields must be stored in new instances of the Tuple class. The result will be a list of Tuples instead of a list of Territories.

Here you find a code snippet, returning a subset of the territory structure in a list of Tuples after the filtering:

// Select only a subset of territory fields as a list of Tuples
var subset3 = myTerritories.Where(t => t.region.StartsWith("B"))
.Select(t => new Tuple<int, string>(t.zip, t.city));

The problem is now, that Tuples are immutable / read only and follwing code would not compile:

subset3.ElementAt(0).Item2 = "Reinach BL";

So, the best solution is, to define a new class simillar to the Tuple class with a propper constructor and use this class instead of the Tuple class. Here the complete sample code:

class Program
{
// Define a class to store data
class Territory { public int zip; public string city; public string region; };

static void Main(string[] args)
{
// Define a sample list of objects to work with
Territory[] myTerritories = new Territory[] {
new Territory{zip=4153,city="Reinach",region="BL"},
new Territory{zip=8304,city="Wallisellen",region="ZH"},
new Territory{zip=3018,city="Bern",region="BE"}};

// Select a complete Territory subset variant 1
var subset1 = from Territory t in myTerritories
where t.region.StartsWith("B")
select t;
// Select a complete Territory subset variant 2
var subset2 = myTerritories.Where(t => t.region.StartsWith("B"));

// Select only a subset of territory fields as a list of Tuples
var subset3 = myTerritories.Where(t => t.region.StartsWith("B"))
.Select(t => new Tuple<int, string>(t.zip, t.city));

// because tuples are immutable/read only, these
// will not compile and therefore it's commented out:
/* subset3.ElementAt(0).Item2 = "Reinach BL"; */

// Select only a subset of territory fields as a list of entities
var subset4 = myTerritories.Where(t => t.region.StartsWith("B"))
.Select(t => new Entity<int, string>(t.zip, t.city));

// because Entity is mutable these will work:
subset4.ElementAt(0).Item2 = "Reinach BL";
}
}
public class Entity<T1, T2>
{
public Entity(T1 t1, T2 t2)
{
Item1 = t1;
Item2 = t2;
}
public T1 Item1 { get; set; }
public T2 Item2 { get; set; }
}
public class Entity<T1, T2, T3>
{
public Entity(T1 t1, T2 t2, T3 t3)
{
Item1 = t1;
Item2 = t2;
Item3 = t3;
}
public T1 Item1 { get; set; }
public T2 Item2 { get; set; }
public T3 Item3 { get; set; }
}
{"ft_lang_label":"__label__en","ft_lang_prob":0.7621706,"math_prob":0.6059418,"size":4705,"snap":"2019-43-2019-47","text_gpt3_token_len":1149,"char_repetition_ratio":0.15145713,"word_repetition_ratio":0.3165761,"special_character_ratio":0.261424,"punctuation_ratio":0.15828878,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.95917296,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2019-10-15T10:03:49Z\",\"WARC-Record-ID\":\"<urn:uuid:6d34a621-46a1-4737-92a3-221dcb264003>\",\"Content-Length\":\"36258\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:4d39f5cf-42cd-4a4e-b071-3dcdcec35574>\",\"WARC-Concurrent-To\":\"<urn:uuid:691f3fbc-996f-479f-ad7c-05bede1aafe9>\",\"WARC-IP-Address\":\"212.243.197.119\",\"WARC-Target-URI\":\"http://www.azuretweaks.com/2016/04/\",\"WARC-Payload-Digest\":\"sha1:ZW267HFR7O4DN5WX6YWI4FK7VAKDJSCA\",\"WARC-Block-Digest\":\"sha1:MBIDC5NOVXYYZMAJNJ4TYDCTF3TBWGA5\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2019/CC-MAIN-2019-43/CC-MAIN-2019-43_segments_1570986657949.34_warc_CC-MAIN-20191015082202-20191015105702-00173.warc.gz\"}"}