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.

Serializing/Deserializing Dictionaries with Tuple as Key

Sometimes you run into things that might look trivial but it just do not work as expected. One such example is when you attempt to serialize/Deserialize a Dictionary with Tuples as the key. For example

var dictionary = new Dictionary<(string, string), int>
{
[("firstName1", "lastName1")] = 5,
[("firstName2", "lastName2")] = 5
};

var json = JsonConvert.SerializeObject(dictionary);
var result = JsonConvert.DeserializeObject<Dictionary<(string, string), string>>(json);

The above code would trow an JsonSerializationException when deserializing. But the good part is, the exception tells you exactly what needs to be done. You need to use an TypeConverter here.

Let’s define our required TypeConverter

public class TupleConverter<T1, T2> : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var elements = Convert.ToString(value).Trim('(').Trim(')').Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return (elements.First(), elements.Last());
}
}

And now, you can alter the above code as

TypeDescriptor.AddAttributes(typeof((string, string)), new TypeConverterAttribute(typeof(TupleConverter<string, string>)));
var json = JsonConvert.SerializeObject(dictionary);
var result = JsonConvert.DeserializeObject<Dictionary<(string, string), string>>(json);

With the magic portion of TypeConverter in place, your code would now work fine. Happy Coding.