15

Is there a saner way to do the following:

public static class ValueTupleAdditions {
  public static IEnumerable<object> ToEnumerable<A, B>(this ValueTuple<A, B> tuple) {
    yield return tuple.Item1;
    yield return tuple.Item2;
  }
  public static IEnumerable<object> ToEnumerable<A, B, C>(this ValueTuple<A, B, C> tuple) {
    yield return tuple.Item1;
    yield return tuple.Item2;
    yield return tuple.Item3;
  }

  [etc]
}

EDIT: Since people are asking for a use case, here you go.

using Xunit;

namespace Whatever {

  public class SomeTestClass {
    public static IEnumerable<(string, Expression<Func<string, string>>, string)> RawTestData() {
      yield return ("Hello", str => str.Substring(3), "lo");
      yield return ("World", str => str.Substring(0, 4), "worl");
    }
    public static IEnumerable<object[]> StringTestData() {
      return RawTestData().Select(vt => new object[] { vt.Item1, vt.Item2, vt.Item3 });
       // would prefer to call RawTestData().Select(vt => vt.ToArray()) here, but it doesn't exist.
    }

    [Theory, MemberData(nameof(StringTestData))]
    public void RunStringTest(string input, Expression<Func<string, string>> func, string expectedOutput) {
      var output = func.Compile()(input);
      Assert.Equal(expectedOutput, output);
    }
  }
}
William Jockusch
  • 26,513
  • 49
  • 182
  • 323
  • 6
    Well, you could create an array... but really, it's a fairly unusual usage of tuples to start with, I'd say. Do you have a concrete use case? – Jon Skeet May 16 '17 at 16:37
  • 1
    yes, xunit testing via theories, you really want the value tuples for your initial inputs, you convert them to object arrays to make xunit happy, then pull them out as their original types in the test method – William Jockusch May 16 '17 at 16:38
  • 1
    What? Post your test code. – Mardoxx May 16 '17 at 16:46
  • The `IEnumerable ` is an enumerable of test method parameters? No explicit conversion here? – Mardoxx May 16 '17 at 16:48
  • 1
    0 down vote It seems you want to transform the tuple into something it is not. That is perfectly ok, but maybe you could create a class for that, that has constructors for all the 8 tuple forms. That way you could have that class enumerate, slice, Count, combine it with yet another tuple, transform it back to a (smaller) tuple and what not. – Ferdinand Swaters May 16 '17 at 16:52

3 Answers3

3

One way to do this is via the ITuple interface.

public interface ITuple
{
    int Length { get; }
    object this[int index] { get; }
}

It is only available in .NET Core 2.0, Mono 5.0 and the next version of .NET Framework (unreleased, following 4.7). It is not (and will never be) available as an add-on to older frameworks via the ValueTuple package.

This API is designed for usage by the C# compiler for future work on patterns.

Julien Couvreur
  • 4,473
  • 1
  • 30
  • 40
2

A bit of reflection:

namespace ConsoleApp1
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    public class Program
    {
        public static void Main()
        {
            var tuple = (1, 2, 3, 4, 5, 6, 7);
            var items = ToEnumerable(tuple);

            foreach (var item in items)
            {
                Console.WriteLine(item);
            }
        }

        private static IEnumerable<object> ToEnumerable(object tuple)
        {
            if (tuple.GetType().GetInterface("ITupleInternal") != null)
            {
                foreach (var prop in tuple.GetType()
                    .GetFields()
                    .Where(x => x.Name.StartsWith("Item")))
                {
                    yield return prop.GetValue(tuple);
                }
            }
            else
            {
                throw new ArgumentException("Not a tuple!");
            }
        }
    }
}
Szer
  • 3,426
  • 3
  • 16
  • 36
  • Thanks for this ! – BillW Jul 14 '17 at 09:34
  • To quote from [Type.GetFields](https://learn.microsoft.com/en-us/dotnet/api/system.type.getfields?view=net-5.0): *"The GetFields method does not return fields in a particular order, such as alphabetical or declaration order. Your code must not depend on the order in which fields are returned, because that order varies.*" Thus, we should probably extract the item number from the field name and `OrderBy` it. – Heinzi Nov 12 '21 at 16:40
2

One way is to use an extension method based on ITuple, see also answer by Julien Couvreur:

public static IEnumerable<T> ToEnumerable<T>( this ITuple tuple ) {
    for ( var n = 0; n < tuple.Length; n++ ) yield return (T)tuple[ n ];
}

sample usage:

var directions = (
    right: (cx: 1, cy: 0),
    down: (cx: 0, cy: 1),
    left: (cx: -1, cy: 0),
    up: (cx: 0, cy: -1)
);
foreach ( var direction in directions.ToEnumerable<(int cx, int cy)>() ) {
    var (cx, cy) = direction;
    TryMovePiece( (x + cx, y + cy) );
}
NoonKnight
  • 153
  • 5
  • 8