September 15, 2017 · roslyn .net tuple csharp ·

Interesting C# 7.x features - 01

In my previous post I briefly mentioned my interest in .NET Core and new C# features. In this post I'd like to explore some various patterns I found interesting in C# 7 and may go a bit deeper into the details of what the compiler generates. My intent is to make a couple of posts relating to these interesting features and in some cases get into the finer details too.

1. Tuple

Introduction

In particular on this post I will start with the new Tuple. This is a ValueTuple which is lightweight and implemented as a struct value type rather than a reference type. It is also mutable rather than readonly and the members are internally stored as fields.

Being a value type means that during runtime, it is stored in the stack and accessed directly. There is a way to transform this value to a reference type Tuple using ToTuple(); extension method.

While you can create a new instance of a ValueTuple using new ValueTuple<T>(), C# offers other ways of creating Tuples by using the static methods Create or using the common mathematical parethesis notation. The type needs generic type parameters for each value that will be stored in the sequence. In C# unlike Python the Tuple is not iterable. I personally think this is a better design. While a Tuple is a sequence, if you are going to have many elements, you are better off using a List<T> or array. To me a Tuple provides a simpler record type. What happens when you call Reverse()? Distortion.

Using new

  // Using the new keyword.
  // Note that the fields will have names such as Item1, Item2, e.t.c based on the number of 
  // generic types included
    var var1 = new ValueTuple<int>();
    var1.Item1 = 5;        

    // Using more than one generic 
    var var2 = new ValueTuple<int, string>(); 
    var2.Item1 = 5
    var2.Item2 = "SomeName";

    // A Cartesian product
    var cart = from a in seq1
               from b in seq2
               select new ValueTuple<int, string>(a, b);

    // Empty Tuple
    var empty = new ValueTuple();

On the Cartesian product above you can see that we created an array of Tuples by using a projection of two arrays a and b.

While you can create a new Tuple without specifying generic parameters as shown by the last part of the code segment above, that tuple does not seem to have any use.

A ValueTuple is a normal type, therefore you can go crazy with it and probe methods such as GetHashCode() which combines the hash codes of its members.

Using parenthesis

The following are the other ways you can create ValueTuple types using the parenthesis symbol.

    // Using semantic names
    (string alpha, string beta) namedLetters1 = ("a", "b");
    var namedLetters2 = (alpha: "a", beta: "b");
    WriteLine($"{namedLetters1.alpha} == {namedLetters2.alpha}");

    // Creating an array of Tuples
    foreach (var item in new[] { (1, 2), (3, 4) })
    {
        WriteLine(item.Item1);
    }

Swapping

Take a look at this.

    (int a, int b) = (1, 2);
    (a, b) = (b, a);

It does look familiar, right? Looks like Python except for the type declaration. I definitely like this.

Deconstruction

The Tuple enables a new concept of deconstruction. This enables you to break an object into pieces that make up a tuple. This is done by using a Deconstruct method and out parameters. This method can be overloaded provided you vary the number of the out parameters. You would obviously need to assign values to them before the end of the method. Consider the following.

    public struct Point
    {
        public Point(double x, double y)
        {
            X = x;
            Y = y;
        }

        public double X { get; }
        public double Y { get; }

        public void Deconstruct(out double x, out double y)
        {
            x = X;
            y = Y;
        }

        // Overloaded deconstruction method
        public void Deconstruct(out double x, out double y, out string name)
        {
            x = X;
            y = Y;
            name = "shape";
        }
    }


The following can then be used to construct and deconstruct above. On deconstructing, you may use the underscore to indicate output positional values you would like to ignore.

    // Create the new Point
    var location = new Point(6, 7);

    // Create a Tuple from deconstructing the instance 
    (double first, double second) = location;
    Console.WriteLine($" X: {first}; Y: {second}");

    // Note that you can ignore some of the return values using _ as shown below
    var (num1, _) = location;

Delegates

One of the most interesting features of Tuples is that it opens new ways of working with data. Lets consider and example where some member of a Tuple is an Action delegate as shown below. This is enabled by the normal way we are able to have delegate members on classes. We can also add an extension methods on the tuple (because we can). Take a closer look and consider what the segment below will print.

    public class Program
    {
        static void Main(string[] args)
        {
            Action Print = () => Console.Out.WriteLine("Lambda Expression");
            var tuple = (5, Print);

            // What will this print?
            tuple.Print();

            // And this?
            tuple.Item2();
        }
    }
    public static class Extensions
    {
        // Note. We are able to create extension methods
        public static void Print<T1, T2>(this ValueTuple<T1, T2> tuple)
        {
            Console.Out.WriteLine("Extension Method");
        }
    }

What will happen if we replace the line with

var tuple = (5, Print: Print);  

Collections

We are also able to put collections. Lets see the following method.

    void Calculate()
    {
        // using local function. A new feature in C#7
        void ProcessInternal((int, IList<object>) items)
        {
            //Some code
        }

        void Process()
        {
            var items = new List<object>();
            var group = (1, items);
            ProcessInternal(group);
        }

        Process();
    }

You are also able to nest Tuples like

    var test = (1, ("Malisa", 2), ("Ncube", "Langton", 5)); // test.Item3.Item2 = Langton

    // With semantic names
    var me = (number: 1, identity: (name: "Malisa", brothers: 2), (lastName: "Ncube", middleName: "Langton", senses: 6));
            var name = me.identity.name;

Cool, right?

Properties

You can also have Tuple properties, like so.

    public class A
    {
        public (int, string) TupleProperty { get; set; }
    }

    // Usage 
    var item = new A
    {
       TupleProperty = (23, string.Empty)
    };

Does it still look like its C#?

Serialization (UPDATE: 20/09/17)

As part of my experiments, I thought it might be interesting to test some Serialization of the Tuple type. The answer is that, you are able to serialize the object that contains a Tuple as long as the Tuple does not contain Delegates.

    var formatter = new BinaryFormatter();
    using (Stream filestream = new FileStream("object.bin", FileMode.Create, FileAccess.Write, FileShare.None))
    {
        formatter.Serialize(filestream, cell);
        filestream.Close();
    }
    //
    using (Stream stream = new FileStream("object.bin", FileMode.Open, FileAccess.Read, FileShare.Read))
    {
        var obj = (Cell)formatter.Deserialize(stream);
        stream.Close();
    }

The class

        [Serializable]
        public class Cell
        {
              public (double, double) Coordinates { get; set; }
        }

Even Json.NET is able to serialize and Deserialize the Tuple type. I imagine that soon we will be able to have ORMs handle these scenarios. I envision data that needs
to be stored as tuples, like Coordinates, Points for maps and Polygons. These are already supported in some databases.

Internals

Lets look at the IL code that the compiler generates when we write the following code.

    var test = (1, "Apples");
    Console.WriteLine(test.Item1);
    Console.ReadLine();

The ldloca instruction pushes the address of the local variable number at the passed index onto the stack, where local variables are numbered 0 onwards. The value pushed on the stack is already aligned correctly for use with instructions like LdindI and StindI. The result is a transient pointer (type *).

Note how the new value type is instantiated and then the field values are loaded.

    // [12 13 - 12 54]
    IL_0001: ldloca.s     test
    IL_0003: initobj      valuetype [System.Runtime]System.ValueTuple`2<int32, string>

    // [13 13 - 13 28]
    IL_0009: ldloca.s     test
    IL_000b: ldc.i4.5     
    IL_000c: stfld        !0/*int32*/ valuetype [System.Runtime]System.ValueTuple`2<int32, string>::Item1

    // [14 13 - 14 35]
    IL_0011: ldloca.s     test
    IL_0013: ldstr        "Apples"
    IL_0018: stfld        !1/*string*/ valuetype [System.Runtime]System.ValueTuple`2<int32, string>::Item2

    // [15 13 - 15 43]
    IL_001d: ldloc.0      // test
    IL_001e: ldfld        !0/*int32*/ valuetype [System.Runtime]System.ValueTuple`2<int32, string>::Item1
    IL_0023: call         void [System.Console]System.Console::WriteLine(int32)
    IL_0028: nop          

    // [16 13 - 16 32]
    IL_0029: call         string [System.Console]System.Console::ReadLine()
    IL_002e: pop          

    // [17 9 - 17 10]
    IL_002f: ret          

Simple, right?

A Hack

WARNING! I have added an extension method which uses reflection to enable enumeration of the items in the Tuple. This of course has many limitations as you still need to cast the objects to a specific type. I would not put this in a system in production.

    // Usage
    var test = (1, "Malisa").GetItems();


    public static class Extensions
    {
        // This only works with a twin
        public static IEnumerable<object> GetItems<T1, T2>(this ValueTuple<T1, T2> tuple)
        {
            ITuple item = tuple;
            var fields = tuple.GetType().GetFields(BindingFlags.Public | 
                                                    BindingFlags.NonPublic | 
                                                    BindingFlags.Instance);
            for (var i = 0; i < item.Length; i++)
            {
                yield return fields[i].GetValue(tuple);
            }
        }

        // Other 
    }

Thanks for reading. Let's see what I can write about on my next post.

Cheers :)

Comments powered by Disqus