4
\$\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\$
5
  • \$\begingroup\$ I'd rather use collections (perhaps immutable, perhaps simple arrays). \$\endgroup\$ Commented Jun 20, 2013 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
    Commented Jun 20, 2013 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\$ Commented Jun 20, 2013 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\$ Commented Jun 20, 2013 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\$ Commented Jun 20, 2013 at 10:58

1 Answer 1

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.

\$\endgroup\$
4
  • \$\begingroup\$ I like this. Shame that we can't create an implicit cast to IEnumerable<T> and all this would work automagically. \$\endgroup\$ Commented Jun 20, 2013 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\$ Commented Jun 20, 2013 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
    Commented Jun 20, 2013 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\$ Commented Aug 30, 2013 at 18:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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