Evil Code #13 : Tuple Deconstruction Assignment

It is not often that I end up writing two back to back posts on Evil Code series, and that too on the same topic. But like I said in the previous post, Tuples are really an interesting topic (if you are reading Jon Skeet’s book, then everything is interesting)

In this post, we will explore a certain behavior of tuples observed during deconstruction assignment

Consider the following code.

var person = new Person{Name="Jia",Age=4};
var personCopy = person;
(person,person.Age) = (new Person{Name="Naina"},0.2);

Console.WriteLine($"Student => Name: {person.Name}, Age: {person.Age}");
Console.WriteLine($"StudentCopy => Name: {personCopy.Name}, Age: {personCopy.Age}");

The output might surprse you at the first glance, but seems reasonable as you attempt to understand what goes behind the deconstruction assignment. Let us look at the output first before we see what happens during the deconstruction.

Student => Name: Naina, Age: 0
StudentCopy => Name: Jia, Age: 0.2

This behavior attributes itself to the 3 stage process involved in the deconstruction assignment.

Note that unlike User defined type deconstruction, which requires invocation of Deconstruct method with appropriate number of parameters.

As Microsoft has put down in the feature description,

The evaluation order can be summarized as: (1) all the side-effects on the left-hand-side, (2) all the Deconstruct invocations (if not tuple), (3) conversions (if needed), and (4) assignments.

From the above, (2 & 3) aren’t part of our concern (it is a tuple and no conversion if required as the source/destination types are the same). In the first step, the side-effect of LHS is evaluated while only in the last step the actual assignments happen.

So in this case, each of the expresion on the Left hand side, is evaluated one by one, and a storage location is reserved for it.After this, the RHS is evaluated, and any conversion to destination type is done if required.

Finally the conversion results or values from the RHS is assigned to the storage location identified in the first step.

This would mean the above code gets roughly translated to

Person person = new Person
{
    Name = "Jia",
    Age = 4.0
};
Person personCopy = person;
// LHS is evaluated and storage for person.age is reserved
Person person2 = person;
//RHS is evaluated and converted if required
Person obj = new Person
{
    Name = "Naina"
};
double num = 0.2;

// assignments
person = obj;
person2.Age = num;

Notice that during the evaluation of LHS, space reservation and evaluation is done only for person.age and not for  person from (person,person.age). This is because when assigning to a property of a variable, an extra step is required to evaluate the value of variable. This step could be skipped when the target is a variable and not property of a variable.

That explains the output we received for the earlier code.

Note: If you are a C# developer, and haven’t read Jon Skeet’s C# in Depth, do it immediately. This book is a MUST read for any C# developer.

Evil Code #12: Tuple Parameter – Overloads and Overrides

Tuples is an interesting subject to go back to due to certain peculiar qualities it possess. In this section, we would address some of them.

To begin with, however, we will address how to tuples behave during method overloads – a pattern it share with rest of .Net. Consider the following code.

public class Foo
{
	public void Bar((int,object) param)
	{
	}
	
	public void Bar((int,dynamic) param)
	{
	}
}

Would the above code compile ? If not, why ?

Well, it doesn’t compile – of course. The compiler would complain that

Type 'Foo' already defines a member called 'Bar' with the same parameter types

This is because there is an identity conversion between object and dynamic, which results in compiler detecting both methods as signature with same parameter types. This is similiar to how it would have been if you had defined an overload as following.

public void Bar(object param)
{
}

public void Bar(dynamic param)
{
}

As one would guess, that isn’t allowed either.

The second scenario we would be checking is with regard to inheritance or interface implementations. Consider the following code.

public interface IBar
{
	void Foo((object left,object right) param);
}

public class Bar:IBar
{
	public void Foo((object left, dynamic right) param)
	{
	}
}

Now considering the identity conversion between object and dynamic, it is easy to guess that this would be allowed. However, the nasty part shows when you vary the name of the tuple variable. Consider the following code, where i have renamed the right parameter in tuple as center.

public interface IBar
{
	void Foo((object left,object right) param);
}

public class Bar:IBar
{
	public void Foo((object left, object center) param)
	{
	}
}

Notice, I have retained the types of each element and arity of the tuple. The only thing that has changed is the name of one of the elements in the tuple. This breaks the code with following exception.

The tuple element names in the signature of method 'Bar.Foo((object left, object center))' must match the tuple element names of interface method 'IBar.Foo((object left, object right))' (including on the return type).

Isn’t that interesting ? I would rather use the word scary. Imagine as a library developer you rename of the tuple parameters in your API and it breaks all the clients.

This would be one change I wish the elite team working .Net manages to fix as this is quite unlike rest of the .Net.

Evil Code #011 : Tuples

It has been long since I wrote a post in Evil Code series. So here is a short one, using the Tuples which was introduced in C# 7.X.

“introduced in C# 7.x” . You must be wondering why I used that phrase. Tuples was there before 7.x right ? Well, the answer is both yes and no. Tuples did exist before 7.x, but the one introduced in 7.x isn’t exactly the same. This newer version is a struct called System.ValueTuple and are noticeably different from its predecesoor in following ways.

  • System.ValueTuple is a struct, while System.Tuple is a reference type.
  • System.ValueTuple is mutable, while System.Tuple is not.
  • System.ValueTuple exposes items via Fields, while System.Tuple exposes items via properties.

You might have already seen a thousand articles on Tuples and have used them in your code base. So I would not go into further details on semantics and usages, but we will explore a special case.

Consider the following code.

var tuple1 = (Item1:1,Item2:2);
var tuple2 = (1,2);
var tuple3 = (Item2:2,Item1:1);

What would be the output of above code ?

Well, it would not even compile. The last would throw an error.

CS8125 Tuple element name 'Item2' is only allowed at position 2.
CS8125 Tuple element name 'Item1' is only allowed at position 1.

Why is it so ? Tuples can be broadly categorized in two ways – tuple literal and tuple types.

Tuple literals has a value and an optional name, while tuple typeshave type and optional name. In both cases, the name is optional and could be anything, except when the naming pattern uses the ItemN syntax.

These are reserved names which the tuple reserves for unnamed Tuple. You could use them as well, however, you are not allowed to alter the order.

Every element in the tuple could be accessed by the name if present, or its position. For acessing the Tuple element with position, .Net uses the naming convension ItemN where N is the position in the Tuple (1 based index).

The first element in the Tuple has to be Item1, the second Item2 and so on. If you alter the naming convention and use Item2 as the first element, the compiler would complain as shown above.

If you are attempting to name the first element as Item2, you are basically creating an ambiguity (should use the word confusion). This is why the naming convension prohibits such a pattern.

That was a rather a simple one right ? But that is the thing I like about the Evil Code series. Most of them are quite simple, but somewhere in the details, there are hidden facts which one as a developer needs to be aware of.