3
\$\begingroup\$

I have

public static class TupleExtensions {

    public static IEnumerable<T> SelectMany<T>(this IEnumerable<Tuple<T, T>> te)
    {
        foreach (var t in te)
        {
            yield return t.Item1;
            yield return t.Item2;
        }
    }

    public static IEnumerable<T> SelectMany<T, U>(this IEnumerable<U> source, Func<U, Tuple<T, T>> fn)
    {
        foreach (var s in source)
        {
            var t = fn(s);
            yield return t.Item1;
            yield return t.Item2;
        }

    } 

    public static IEnumerable<T> SelectMany<T, U>(this IEnumerable<U> source, Func<U, Tuple<T, T, T>> fn)
    {
        foreach (var s in source)
        {
            var t = fn(s);
            yield return t.Item1;
            yield return t.Item2;
            yield return t.Item3;
        }

    }

    public static IEnumerable<T> SelectMany<T, U>(this IEnumerable<U> source, Func<U, Tuple<T, T, T,T>> fn)
    {
        foreach (var s in source)
        {
            var t = fn(s);
            yield return t.Item1;
            yield return t.Item2;
            yield return t.Item3;
            yield return t.Item4;
        }

    }
}

I can then do as a trivial example

IEnumerable<int> source = ...
source.SelectMany(i=>Tuple.Create(i, i+1, i+2))

Obviously the tuple must have the same types throughout. My use case is where I have a mathematical solver which will always return two solutions for a given input. Returning a list makes it unclear how many solutions would be returned.

However in the end I would want all my solutions merged. For example

static Tuple<Point,Point> Intersect(this Shape shape, Line other);

Shape shape = ...
IEnumerable<Line> lines = ...

IEnumerable<Point> intersections = lines.SelectMany(line=>shape.Intersect(line));

Is there some good reason not to treat Tuple<T,T,T....> as a container that we can flatten?

\$\endgroup\$
  • \$\begingroup\$ I'd rather use collections (perhaps immutable, perhaps simple arrays). \$\endgroup\$ – CodesInChaos Jun 20 '13 at 9:58
  • 2
    \$\begingroup\$ No, there's no good reason not to consider tuples this way. If it seems "wrong" somehow, it's only because C# has such limited support for tuples. In a functional language, or one with better tuple support, this would seem very natural. \$\endgroup\$ – MattDavey Jun 20 '13 at 10:00
  • \$\begingroup\$ @CodesInChaos the problem with collections is that the length is not considered part of the type. If my function should always return two values but I return a List<Point> should the client of that code put in a check that there are in fact two values or risk an index out of bounds exception? \$\endgroup\$ – bradgonesurfing Jun 20 '13 at 10:06
  • \$\begingroup\$ if you really want length to be part of the type, then create a custom type. Else simply go with T[] or ReadOnlyCollection<T>. \$\endgroup\$ – CodesInChaos Jun 20 '13 at 10:10
  • 1
    \$\begingroup\$ @CodesInChaos Can you explain why you think Tuple is wrong for this? In C++ I could create a type with an integer type parameter with a recursive template definition. In C# that is not possible. How would you make a generic Container<T,length> type other than with Tuple? \$\endgroup\$ – bradgonesurfing Jun 20 '13 at 10:58
2
\$\begingroup\$

In general, I think this pattern makes sense, because it clearly expresses what the method returns and lets you easily do the computation you want to do with the result.

Some thoughts:

  1. I don't see any reason why your extension methods should only work on a collection of Tuples and not on a single Tuple. Because of that, I would probably write extension methods that convert a single Tuple to IEnumerable:

    public static IEnumerable<T> ToEnumerable<T>(this Tuple<T, T> tuple)
    {
        yield return tuple.Item1;
        yield return tuple.Item2;
    }
    

    Since this would make the code you showed more complicated, it might make sense to also keep SelectMany(), but express it using ToEnumerable():

    public static IEnumerable<T> SelectMany<T>(
        this IEnumerable<Tuple<T, T>> tuples)
    {
        return tuples.SelectMany(t => t.ToEnumerable());
    }
    
    public static IEnumerable<T> SelectMany<T, U>(
        this IEnumerable<U> source, Func<U, Tuple<T, T>> selector)
    {
        return source.SelectMany(x => selector(x).ToEnumerable());
    }
    
  2. It might make sense to create custom types that mean “pair (triple, …) of items of the same type”. As a side benefit, you wouldn't need custom ToEnumerable() or SelectMany() extensions, since those types could implement IEnumerable<T> directly.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I like this. Shame that we can't create an implicit cast to IEnumerable<T> and all this would work automagically. \$\endgroup\$ – bradgonesurfing Jun 20 '13 at 11:35
  • \$\begingroup\$ Isn't all you need ToEnumerable? IEnumerable<Point> intersections = lines.SelectMany(line=>shape.Intersect(line).ToEnumerable()); looks all right to me. What does all the rest give? \$\endgroup\$ – abuzittin gillifirca Jun 20 '13 at 13:32
  • \$\begingroup\$ @abuzittingillifirca Yes, that works fine, but I think an overload of SelectMany() makes sense to make the code shorter, especially since the Tuple can be considered a sequence, so the SelectMany() does logically make sense. \$\endgroup\$ – svick Jun 20 '13 at 13:54
  • 1
    \$\begingroup\$ @bradgonesurfing To be consistent with existing LINQ APIs you should use the semantics AsEnumerable instead of ToEnumerable : msdn.microsoft.com/en-us/library/bb335435.aspx , msdn.microsoft.com/en-us/library/…, stackoverflow.com/questions/9873930/… , \$\endgroup\$ – smartcaveman Aug 30 '13 at 18:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.