-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Currently we have 2 families of types to represent tuples: classes System.Tuple<T1, T2, ...>
and mutable structs System.ValueTuple<T1, T2, ...>
. There are a family of extension methods to convert between them: System.TupleExtensions.ToTuple<...>(...)
and System.TupleExtensions.ToValueTuple<...>(...)
. Both families of tuples are already stuck in some APIs for good. Some users have their own tuple types from before tuples were introduced into the standard library.
To improve interoperability, avoid making unnecessary copies, and support covariant conversions, I propose to introduce a family of covariant interfaces System.ITuple<out T1, out T2, ...>
that will be implemented both by Tuple<...>
and ValueTuple<...>
. Users also can make their custom tuple types implement these interfaces. Here is a sketch of how they might look:
using System;
using System.Collections;
namespace System
{
// Inherit interfaces shared by both Tuple<...> and ValueTuple<...> types
// They are not strictly necessary; it is debatable whether they are needed here
public interface ITuple<out T1, out T2> : IStructuralEquatable, IStructuralComparable, IComparable, ITuple
{
T1 Item1 { get; }
T2 Item2 { get; }
}
public class Tuple<T1, T2> : ITuple<T1, T2>, ITupleInternal
{
public T1 Item1 => this.m_Item1; // already exists
public T2 Item2 => this.m_Item2; // already exists
// other existing members...
}
public struct ValueTuple<T1, T2> : ITuple<T1, T2>, IEquatable<ValueTuple<T1, T2>>, IComparable<ValueTuple<T1, T2>>, IValueTupleInternal
{
T1 ITuple<T1, T2>.Item1 => this.Item1; // explicit interface implementation to avoid conflict with existing field Item1
T2 ITuple<T1, T2>.Item2 => this.Item2; // explicit interface implementation to avoid conflict with existing field Item2
// other existing members...
}
}
The existing static class System.TupleExtensions
could add extension methods to support these new interfaces:
namespace System
{
public static class TupleExtensions
{
public static (T1, T2) ToValueTuple<T1, T2>(this ITuple<T1, T2> value) =>
value is null
? throw new ArgumentNullException(nameof(value))
: (value.Item1, value.Item2);
public static Tuple<T1, T2> ToTuple<T1, T2>(this ITuple<T1, T2> value) =>
value is null
? throw new ArgumentNullException(nameof(value))
: value as Tuple<T1, T2> ?? Tuple.Create(value.Item1, value.Item2);
public static void Deconstruct<T1, T2>(this ITuple<T1, T2> value, out T1 item1, out T2 item2)
{
if (value is null)
throw new ArgumentNullException(nameof(value));
item1 = value.Item1;
item2 = value.Item2;
}
// other existing members...
}
}