This is the tenth post in my ongoing series covering the abstractions in Sasa. Previous posts:
- Sasa.Parsing - type-safe, extensible lexing and parsing framework
- Sasa.Dynamics - type-safe polytypic/reflective programming
- Sasa.Func - Type-Safe Delegate Combinators
- Sasa.Option - Handling Optional Values
- Sasa.Result - Handling Exceptional Values
- Sasa.Numbers - Generic Number Extensions
- Sasa.Strings - General String Extensions
- Sasa.Types - Runtime Types And CLR Metadata
- Sasa.Weak - Typed Weak References
It's common to return multiple values from functions, and there are two common ways to do so in .NET: out parameters, and ordinary objects like structs and classes.
Unfortunately, defining a whole class just to return two or three values is often overkill. Also, out parameters have some limitations, like inability to use them in lambdas, even when it's safe to do so.
Enter tuples, which are types containing only generic parameters whose only purpose is to group some items together to address these difficulties. Sasa's ITuple interfaces [1, 2, 3, 4] define the abstract contracts satisfied by Sasa's tuple types. However, unlike .NET's newly released tuples, Sasa's implementation tuples don't use the "tuple" name. They are instead split into Pair, Triple, and Quad because I found the shared "tuple" name often confusing when auditing code. Particularly where nested generics are concerned, like enumerable sequences of tuples, it's often hard to distinguish Tuple<string,int,int> from Tuple<string,int,int,int> while refactoring.
Sasa.ITuple<T>.First
Sasa.ITuple<T>.First is a property that permits clients to access the first item in a tuple of arbitrary size:
var x = Tuples.Create(3, "foo"); Console.WriteLine(x.First); // output: // 3
This property is implemented by Pair, Triple and Quad.
Sasa.ITuple<T0, T1>.Second
Sasa.ITuple<T0, T1>.Second is a property that permits clients to access the second item in a tuple of arbitrary size:
var x = Tuples.Create(3, "foo"); Console.WriteLine(x.Second); // output: // foo
This property is implemented by Pair, Triple and Quad.
Sasa.ITuple<T0, T1, T2>.Third
Sasa.ITuple<T0, T1, T2>.Third is a property that permits clients to access the third item in a tuple of arbitrary size:
var x = Tuples.Create(3, "foo", 123.4M); Console.WriteLine(x.Third); // output: // 123.4
This property is implemented by Triple and Quad.
Sasa.ITuple<T0, T1, T2>.Fourth
Sasa.ITuple<T0, T1, T2>.Fourth is a property that permits clients to access the fourth item in a tuple of arbitrary size:
var x = Tuples.Create(3, "foo", 123.4M, "hello world!"); Console.WriteLine(x.Fourth); // output: // hello world!
This property is implemented by Quad.
Sasa.Tuples.Create
Sasa.Tuples.Create is a set of overloaded static methods that to conveniently create tuples exploiting C#'s type inference:
var quad = Tuples.Create(3, "foo", 123.4M, "hello world!"); var triple = Tuples.Create(3, "foo", 123.4M); var pair = Tuples.Create(3, "foo");
Sasa.Tuples.Keyed
Sasa.Tuples.Keyed is a static method to create a System.Collections.Generic.KeyValuePair<TKey, TValue>. It exploits C#'s type inference to make it easy to create key value pairs without having to explicitly specify all the type information:
var dict = new Dictionary<int, string>(); dict.Add(Tuples.Keyed(3, "foo"));
Sasa.Pair<T0, T1>.Bind
Sasa.Pair<T0, T1>.Bind is a method that extracts all of a Pair's values in one step using "out" parameters. So even if you prefer out parameters to tuples, you can easily use an interface that uses Sasa's tuples:
int i; string foo; Tuples.Create(3, "foo") .Bind(out i, out foo);
Sasa.Triple<T0, T1, T2>.Bind
Sasa.Triple<T0, T1, T2>.Bind is a method that extracts all of a Triple's values in one step using "out" parameters. So even if you prefer out parameters to tuples, you can easily use an interface that uses Sasa's tuples:
int i; string foo; decimal d; Tuples.Create(3, "foo", 123.4M) .Bind(out i, out foo, out d);
Sasa.Quad<T0, T1, T2, T3>.Bind
Sasa.Quad<T0, T1, T2, T3>.Bind is a method that extracts all of a Quad's values in one step using "out" parameters. So even if you prefer out parameters to tuples, you can easily use an interface that uses Sasa's tuples:
int i; string foo; decimal d; Tuples.Create(3, "foo", 123.4M) .Bind(out i, out foo, out d);
Sasa.Pair<T0, T1>.ToKeyValue
Sasa.Pair<T0, T1>.ToKeyValue is a method that converts a Pair into a KeyValuePair:
var dict = new Dictionary<int, string>(); dict.Add(Tuples.Create(3, "foo").ToKeyValue());
Closing Remarks
All of the tuple implementations implement equality, GetHashCode, and comparison overloads accounting for each encapsulated element. The tuples are also explicitly defined as fully immutable via the readonly qualifier on their fields. This means that Sasa.Dynamics.Types.MaybeMutable will return false if every generic argument is similarly immutable.
I stopped at Quad<T0, T1, T2, T3> because beyond four values you should probably be creating a specific class to encapsulate the return values, or defining your function to take a callback that creates the specific type you want, ie. in the same fashion as SelectMany.
Comments