Convert ValueTuple to IEnumerable
Asked Answered
S

3

16

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);
    }
  }
}
Strander answered 16/5, 2017 at 16:35 Comment(5)
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?Duplessis
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 methodStrander
What? Post your test code.Jolynjolynn
The IEnumerable <object[]> is an enumerable of test method parameters? No explicit conversion here?Jolynjolynn
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.Sheply
S
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.

Stupefy answered 1/8, 2017 at 19:30 Comment(0)
K
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!");
            }
        }
    }
}
Krone answered 16/5, 2017 at 16:58 Comment(2)
Thanks for this !Defoliate
To quote from Type.GetFields: "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.Hospital
G
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) );
}
Gombroon answered 12/11, 2021 at 16:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.