diff --git a/src/System.ValueTuple/src/System/ValueTuple/TupleExtensions.cs b/src/System.ValueTuple/src/System/ValueTuple/TupleExtensions.cs index 096df957124a..9cd91201fc2d 100644 --- a/src/System.ValueTuple/src/System/ValueTuple/TupleExtensions.cs +++ b/src/System.ValueTuple/src/System/ValueTuple/TupleExtensions.cs @@ -920,7 +920,7 @@ public static Tuple CreateLong(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) where TRest : struct => + private static ValueTuple CreateLong(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) where TRest : struct, ITuple => new ValueTuple(item1, item2, item3, item4, item5, item6, item7, rest); private static Tuple CreateLongRef(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) => diff --git a/src/System.ValueTuple/src/System/ValueTuple/ValueTuple.cs b/src/System.ValueTuple/src/System/ValueTuple/ValueTuple.cs index e8fd6dd28cbe..4f68b9a17e16 100644 --- a/src/System.ValueTuple/src/System/ValueTuple/ValueTuple.cs +++ b/src/System.ValueTuple/src/System/ValueTuple/ValueTuple.cs @@ -8,13 +8,28 @@ namespace System { + /// + /// This interface is required for types that want to be indexed into by dynamic patterns. + /// + public interface ITuple + { + /// + /// The number of positions in this data structure. + /// + int Length { get; } + + /// + /// Get the element at position . + /// + object this[int index] { get; } + } + /// /// Helper so we can call some tuple methods recursively without knowing the underlying types. /// - internal interface ITupleInternal + internal interface ITupleInternal : ITuple { int GetHashCode(IEqualityComparer comparer); - int Size { get; } string ToStringEnd(); } @@ -27,7 +42,7 @@ internal interface ITupleInternal /// - their members (such as Item1, Item2, etc) are fields rather than properties. /// public struct ValueTuple - : IEquatable, IStructuralEquatable, IStructuralComparable, IComparable, IComparable, ITupleInternal + : IEquatable, IStructuralEquatable, IStructuralComparable, IComparable, IComparable, ITupleInternal, ITuple { /// /// Returns a value that indicates whether the current instance is equal to a specified object. @@ -69,7 +84,7 @@ int IComparable.CompareTo(object other) /// /// A signed number indicating the relative values of this instance and . /// Returns less than zero if this instance is less than , zero if this - /// instance is equal to , and greater than zero if this instance is greater + /// instance is equal to , and greater than zero if this instance is greater /// than . /// public int CompareTo(ValueTuple other) @@ -123,7 +138,21 @@ string ITupleInternal.ToStringEnd() return ")"; } - int ITupleInternal.Size => 0; + /// + /// The number of positions in this data structure. + /// + int ITuple.Length => 0; + + /// + /// Get the element at position . + /// + object ITuple.this[int index] + { + get + { + throw new IndexOutOfRangeException(); + } + } /// Creates a new struct 0-tuple. /// A 0-tuple. @@ -282,7 +311,7 @@ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int /// Represents a 1-tuple, or singleton, as a value type. /// The type of the tuple's only component. public struct ValueTuple - : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal, ITuple { /// /// The current instance's first component. @@ -359,7 +388,7 @@ int IComparable.CompareTo(object other) /// /// A signed number indicating the relative values of this instance and . /// Returns less than zero if this instance is less than , zero if this - /// instance is equal to , and greater than zero if this instance is greater + /// instance is equal to , and greater than zero if this instance is greater /// than . /// public int CompareTo(ValueTuple other) @@ -419,7 +448,25 @@ string ITupleInternal.ToStringEnd() return Item1?.ToString() + ")"; } - int ITupleInternal.Size => 1; + /// + /// The number of positions in this data structure. + /// + int ITuple.Length => 1; + + /// + /// Get the element at position . + /// + object ITuple.this[int index] + { + get + { + if (index != 0) + { + throw new IndexOutOfRangeException(); + } + return Item1; + } + } } /// @@ -428,7 +475,7 @@ string ITupleInternal.ToStringEnd() /// The type of the tuple's first component. /// The type of the tuple's second component. public struct ValueTuple - : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal, ITuple { /// /// The current instance's first component. @@ -530,7 +577,7 @@ int IComparable.CompareTo(object other) /// /// A signed number indicating the relative values of this instance and . /// Returns less than zero if this instance is less than , zero if this - /// instance is equal to , and greater than zero if this instance is greater + /// instance is equal to , and greater than zero if this instance is greater /// than . /// public int CompareTo(ValueTuple other) @@ -604,7 +651,29 @@ string ITupleInternal.ToStringEnd() return Item1?.ToString() + ", " + Item2?.ToString() + ")"; } - int ITupleInternal.Size => 2; + /// + /// The number of positions in this data structure. + /// + int ITuple.Length => 2; + + /// + /// Get the element at position . + /// + object ITuple.this[int index] + { + get + { + switch (index) + { + case 0: + return Item1; + case 1: + return Item2; + default: + throw new IndexOutOfRangeException(); + } + } + } } /// @@ -614,7 +683,7 @@ string ITupleInternal.ToStringEnd() /// The type of the tuple's second component. /// The type of the tuple's third component. public struct ValueTuple - : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal, ITuple { /// /// The current instance's first component. @@ -705,7 +774,7 @@ int IComparable.CompareTo(object other) /// /// A signed number indicating the relative values of this instance and . /// Returns less than zero if this instance is less than , zero if this - /// instance is equal to , and greater than zero if this instance is greater + /// instance is equal to , and greater than zero if this instance is greater /// than . /// public int CompareTo(ValueTuple other) @@ -785,7 +854,31 @@ string ITupleInternal.ToStringEnd() return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ")"; } - int ITupleInternal.Size => 3; + /// + /// The number of positions in this data structure. + /// + int ITuple.Length => 3; + + /// + /// Get the element at position . + /// + object ITuple.this[int index] + { + get + { + switch (index) + { + case 0: + return Item1; + case 1: + return Item2; + case 2: + return Item3; + default: + throw new IndexOutOfRangeException(); + } + } + } } /// @@ -796,7 +889,7 @@ string ITupleInternal.ToStringEnd() /// The type of the tuple's third component. /// The type of the tuple's fourth component. public struct ValueTuple - : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal, ITuple { /// /// The current instance's first component. @@ -895,7 +988,7 @@ int IComparable.CompareTo(object other) /// /// A signed number indicating the relative values of this instance and . /// Returns less than zero if this instance is less than , zero if this - /// instance is equal to , and greater than zero if this instance is greater + /// instance is equal to , and greater than zero if this instance is greater /// than . /// public int CompareTo(ValueTuple other) @@ -983,7 +1076,33 @@ string ITupleInternal.ToStringEnd() return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ")"; } - int ITupleInternal.Size => 4; + /// + /// The number of positions in this data structure. + /// + int ITuple.Length => 4; + + /// + /// Get the element at position . + /// + object ITuple.this[int index] + { + get + { + switch (index) + { + case 0: + return Item1; + case 1: + return Item2; + case 2: + return Item3; + case 3: + return Item4; + default: + throw new IndexOutOfRangeException(); + } + } + } } /// @@ -995,7 +1114,7 @@ string ITupleInternal.ToStringEnd() /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. public struct ValueTuple - : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal, ITuple { /// /// The current instance's first component. @@ -1102,7 +1221,7 @@ int IComparable.CompareTo(object other) /// /// A signed number indicating the relative values of this instance and . /// Returns less than zero if this instance is less than , zero if this - /// instance is equal to , and greater than zero if this instance is greater + /// instance is equal to , and greater than zero if this instance is greater /// than . /// public int CompareTo(ValueTuple other) @@ -1198,7 +1317,35 @@ string ITupleInternal.ToStringEnd() return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ")"; } - int ITupleInternal.Size => 5; + /// + /// The number of positions in this data structure. + /// + int ITuple.Length => 5; + + /// + /// Get the element at position . + /// + object ITuple.this[int index] + { + get + { + switch (index) + { + case 0: + return Item1; + case 1: + return Item2; + case 2: + return Item3; + case 3: + return Item4; + case 4: + return Item5; + default: + throw new IndexOutOfRangeException(); + } + } + } } /// @@ -1211,7 +1358,7 @@ string ITupleInternal.ToStringEnd() /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. public struct ValueTuple - : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal, ITuple { /// /// The current instance's first component. @@ -1326,7 +1473,7 @@ int IComparable.CompareTo(object other) /// /// A signed number indicating the relative values of this instance and . /// Returns less than zero if this instance is less than , zero if this - /// instance is equal to , and greater than zero if this instance is greater + /// instance is equal to , and greater than zero if this instance is greater /// than . /// public int CompareTo(ValueTuple other) @@ -1430,7 +1577,37 @@ string ITupleInternal.ToStringEnd() return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ")"; } - int ITupleInternal.Size => 6; + /// + /// The number of positions in this data structure. + /// + int ITuple.Length => 6; + + /// + /// Get the element at position . + /// + object ITuple.this[int index] + { + get + { + switch (index) + { + case 0: + return Item1; + case 1: + return Item2; + case 2: + return Item3; + case 3: + return Item4; + case 4: + return Item5; + case 5: + return Item6; + default: + throw new IndexOutOfRangeException(); + } + } + } } /// @@ -1444,7 +1621,7 @@ string ITupleInternal.ToStringEnd() /// The type of the tuple's sixth component. /// The type of the tuple's seventh component. public struct ValueTuple - : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal, ITuple { /// /// The current instance's first component. @@ -1567,7 +1744,7 @@ int IComparable.CompareTo(object other) /// /// A signed number indicating the relative values of this instance and . /// Returns less than zero if this instance is less than , zero if this - /// instance is equal to , and greater than zero if this instance is greater + /// instance is equal to , and greater than zero if this instance is greater /// than . /// public int CompareTo(ValueTuple other) @@ -1679,7 +1856,39 @@ string ITupleInternal.ToStringEnd() return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ")"; } - int ITupleInternal.Size => 7; + /// + /// The number of positions in this data structure. + /// + int ITuple.Length => 7; + + /// + /// Get the element at position . + /// + object ITuple.this[int index] + { + get + { + switch (index) + { + case 0: + return Item1; + case 1: + return Item2; + case 2: + return Item3; + case 3: + return Item4; + case 4: + return Item5; + case 5: + return Item6; + case 6: + return Item7; + default: + throw new IndexOutOfRangeException(); + } + } + } } /// @@ -1694,8 +1903,8 @@ string ITupleInternal.ToStringEnd() /// The type of the tuple's seventh component. /// The type of the tuple's eigth component. public struct ValueTuple - : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal - where TRest : struct + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal, ITuple + where TRest : struct, ITuple { /// /// The current instance's first component. @@ -1831,7 +2040,7 @@ int IComparable.CompareTo(object other) /// /// A signed number indicating the relative values of this instance and . /// Returns less than zero if this instance is less than , zero if this - /// instance is equal to , and greater than zero if this instance is greater + /// instance is equal to , and greater than zero if this instance is greater /// than . /// public int CompareTo(ValueTuple other) @@ -1914,7 +2123,7 @@ public override int GetHashCode() EqualityComparer.Default.GetHashCode(Item7)); } - int size = rest.Size; + int size = rest.Length; if (size >= 8) { return rest.GetHashCode(); } // In this case, the rest member has less than 8 elements so we need to combine some our elements with the elements in rest @@ -1986,7 +2195,7 @@ private int GetHashCodeCore(IEqualityComparer comparer) comparer.GetHashCode(Item7)); } - int size = rest.Size; + int size = rest.Length; if (size >= 8) { return rest.GetHashCode(comparer); } // In this case, the rest member has less than 8 elements so we need to combine some our elements with the elements in rest @@ -2060,12 +2269,43 @@ string ITupleInternal.ToStringEnd() } } - int ITupleInternal.Size + /// + /// The number of positions in this data structure. + /// + int ITuple.Length + { + get + { + return 7 + Rest.Length; + } + } + + /// + /// Get the element at position . + /// + object ITuple.this[int index] { get { - ITupleInternal rest = Rest as ITupleInternal; - return rest == null ? 8 : 7 + rest.Size; + switch (index) + { + case 0: + return Item1; + case 1: + return Item2; + case 2: + return Item3; + case 3: + return Item4; + case 4: + return Item5; + case 5: + return Item6; + case 6: + return Item7; + } + + return Rest[index - 7]; } } } diff --git a/src/System.ValueTuple/tests/ValueTuple/UnitTests.cs b/src/System.ValueTuple/tests/ValueTuple/UnitTests.cs index 3ebe247158a7..3d4bfc708616 100644 --- a/src/System.ValueTuple/tests/ValueTuple/UnitTests.cs +++ b/src/System.ValueTuple/tests/ValueTuple/UnitTests.cs @@ -724,6 +724,11 @@ public static void ZeroTuples() Assert.Throws(() => ((IStructuralComparable)a).CompareTo("string", DummyTestComparer.Instance)); Assert.Equal("(1, 2, 3, 4, 5, 6, 7, )", CreateLong(1, 2, 3, 4, 5, 6, 7, new ValueTuple()).ToString()); + + ITuple it = ValueTuple.Create(); + Assert.Throws(() => it[-1]); + Assert.Throws(() => it[0]); + Assert.Throws(() => it[1]); } [Fact] @@ -748,6 +753,11 @@ public static void OneTuples() var tupleWithNull = new Tuple(null); Assert.Equal("()", vtWithNull.ToString()); Assert.Equal(tupleWithNull.ToString(), vtWithNull.ToString()); + + ITuple it = ValueTuple.Create(1); + Assert.Throws(() => it[-1]); + Assert.Equal(1, it[0]); + Assert.Throws(() => it[1]); } [Fact] @@ -773,6 +783,12 @@ public static void TwoTuples() var tupleWithNull = new Tuple(null, null); Assert.Equal("(, )", vtWithNull.ToString()); Assert.Equal(tupleWithNull.ToString(), vtWithNull.ToString()); + + ITuple it = ValueTuple.Create(1, 2); + Assert.Throws(() => it[-1]); + Assert.Equal(1, it[0]); + Assert.Equal(2, it[1]); + Assert.Throws(() => it[2]); } [Fact] @@ -800,6 +816,13 @@ public static void ThreeTuples() var tupleWithNull = new Tuple(null, null, null); Assert.Equal("(, , )", vtWithNull.ToString()); Assert.Equal(tupleWithNull.ToString(), vtWithNull.ToString()); + + ITuple it = ValueTuple.Create(1, 2, 3); + Assert.Throws(() => it[-1]); + Assert.Equal(1, it[0]); + Assert.Equal(2, it[1]); + Assert.Equal(3, it[2]); + Assert.Throws(() => it[3]); } [Fact] @@ -830,6 +853,14 @@ public static void FourTuples() var tupleWithNull = new Tuple(null, null, null, null); Assert.Equal("(, , , )", vtWithNull.ToString()); Assert.Equal(tupleWithNull.ToString(), vtWithNull.ToString()); + + ITuple it = ValueTuple.Create(1, 2, 3, 4); + Assert.Throws(() => it[-1]); + Assert.Equal(1, it[0]); + Assert.Equal(2, it[1]); + Assert.Equal(3, it[2]); + Assert.Equal(4, it[3]); + Assert.Throws(() => it[4]); } [Fact] @@ -862,6 +893,15 @@ public static void FiveTuples() var tupleWithNull = new Tuple(null, null, null, null, null); Assert.Equal("(, , , , )", vtWithNull.ToString()); Assert.Equal(tupleWithNull.ToString(), vtWithNull.ToString()); + + ITuple it = ValueTuple.Create(1, 2, 3, 4, 5); + Assert.Throws(() => it[-1]); + Assert.Equal(1, it[0]); + Assert.Equal(2, it[1]); + Assert.Equal(3, it[2]); + Assert.Equal(4, it[3]); + Assert.Equal(5, it[4]); + Assert.Throws(() => it[5]); } [Fact] @@ -896,6 +936,16 @@ public static void SixTuples() var tupleWithNull = new Tuple(null, null, null, null, null, null); Assert.Equal("(, , , , , )", vtWithNull.ToString()); Assert.Equal(tupleWithNull.ToString(), vtWithNull.ToString()); + + ITuple it = ValueTuple.Create(1, 2, 3, 4, 5, 6); + Assert.Throws(() => it[-1]); + Assert.Equal(1, it[0]); + Assert.Equal(2, it[1]); + Assert.Equal(3, it[2]); + Assert.Equal(4, it[3]); + Assert.Equal(5, it[4]); + Assert.Equal(6, it[5]); + Assert.Throws(() => it[6]); } [Fact] @@ -932,9 +982,20 @@ public static void SevenTuples() var tupleWithNull = new Tuple(null, null, null, null, null, null, null); Assert.Equal("(, , , , , , )", vtWithNull.ToString()); Assert.Equal(tupleWithNull.ToString(), vtWithNull.ToString()); + + ITuple it = ValueTuple.Create(1, 2, 3, 4, 5, 6, 7); + Assert.Throws(() => it[-1]); + Assert.Equal(1, it[0]); + Assert.Equal(2, it[1]); + Assert.Equal(3, it[2]); + Assert.Equal(4, it[3]); + Assert.Equal(5, it[4]); + Assert.Equal(6, it[5]); + Assert.Equal(7, it[6]); + Assert.Throws(() => it[7]); } - public static ValueTuple CreateLong(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) where TRest : struct => + public static ValueTuple CreateLong(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) where TRest : struct, ITuple => new ValueTuple(item1, item2, item3, item4, item5, item6, item7, rest); public static Tuple CreateLongRef(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) => @@ -1009,6 +1070,18 @@ public static void EightTuples() var tupleWithNull = new Tuple>(null, null, null, null, null, null, null, new Tuple(null)); Assert.Equal("(, , , , , , , )", vtWithNull.ToString()); Assert.Equal(tupleWithNull.ToString(), vtWithNull.ToString()); + + ITuple it = CreateLong(1, 2, 3, 4, 5, 6, 7, ValueTuple.Create(8)); + Assert.Throws(() => it[-1]); + Assert.Equal(1, it[0]); + Assert.Equal(2, it[1]); + Assert.Equal(3, it[2]); + Assert.Equal(4, it[3]); + Assert.Equal(5, it[4]); + Assert.Equal(6, it[5]); + Assert.Equal(7, it[6]); + Assert.Equal(8, it[7]); + Assert.Throws(() => it[8]); } [Fact] @@ -1074,12 +1147,11 @@ public static void LongTuplesWithNull() [Fact] public static void EightTuplesWithBadRest() { - var d = default(ValueTuple); + var d = default(ValueTuple); d.Item1 = 1; - d.Rest = 42; Assert.Equal(35937, d.GetHashCode()); Assert.Equal(35937, ((IStructuralEquatable)d).GetHashCode()); - Assert.Equal("(1, 0, 0, 0, 0, 0, 0, 42)", d.ToString()); + Assert.Equal("(1, 0, 0, 0, 0, 0, 0, BadTuple)", d.ToString()); Assert.Equal(35937, CreateLong(1, 2, 3, 4, 5, 6, 7, d).GetHashCode()); @@ -1087,7 +1159,48 @@ public static void EightTuplesWithBadRest() Assert.Equal(ValueTuple.Create(1, 0, 0, 0, 0, 0, 0).GetHashCode(), d.GetHashCode()); Assert.Equal(((IStructuralEquatable)ValueTuple.Create(1, 0, 0, 0, 0, 0, 0)).GetHashCode(TestEqualityComparer.Instance), ((IStructuralEquatable)d).GetHashCode(TestEqualityComparer.Instance)); - Assert.Equal("(1, 2, 3, 4, 5, 6, 7, 1, 0, 0, 0, 0, 0, 0, 42)", CreateLong(1, 2, 3, 4, 5, 6, 7, d).ToString()); + Assert.Equal("(1, 2, 3, 4, 5, 6, 7, 1, 0, 0, 0, 0, 0, 0, BadTuple)", CreateLong(1, 2, 3, 4, 5, 6, 7, d).ToString()); + + Assert.Throws(() => CreateLong(1, 2, 3, 4, 5, 6, 7, new BadTuple())); + + ITuple it = d; + Assert.Throws(() => it[-1]); + Assert.Equal(1, it[0]); + Assert.Equal(0, it[1]); + Assert.Equal(0, it[2]); + Assert.Equal(0, it[3]); + Assert.Equal(0, it[4]); + Assert.Equal(0, it[5]); + Assert.Equal(0, it[6]); + Assert.Equal(42, it[7]); + Assert.Throws(() => it[8]); + } + + private struct BadTuple : ITuple + { + // doesn't implement ITupleInternal + + int ITuple.Length => 1; + + object ITuple.this[int index] + { + get + { + if (index == 0) + { + return 42; + } + else + { + throw new IndexOutOfRangeException(); + } + } + } + + public override string ToString() + { + return "BadTuple"; + } } private class TestClass : IComparable