HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

I've started Tuples to LINQ and I'm not sure if it's an anti pattern

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
linqstartedantisureandnottuplespattern

Problem

I have

public static class TupleExtensions {

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

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

    } 

    public static IEnumerable SelectMany(this IEnumerable source, Func> 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 SelectMany(this IEnumerable source, Func> 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 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 Intersect(this Shape shape, Line other);

Shape shape = ...
IEnumerable lines = ...

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


Is there some good reason not to treat Tuple as a container that we can flatten?

Solution

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:

-
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 ToEnumerable(this Tuple 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 SelectMany(
    this IEnumerable> tuples)
{
    return tuples.SelectMany(t => t.ToEnumerable());
}

public static IEnumerable SelectMany(
    this IEnumerable source, Func> selector)
{
    return source.SelectMany(x => selector(x).ToEnumerable());
}


-
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 directly.

Code Snippets

public static IEnumerable<T> ToEnumerable<T>(this Tuple<T, T> tuple)
{
    yield return tuple.Item1;
    yield return tuple.Item2;
}
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());
}

Context

StackExchange Code Review Q#27586, answer score: 2

Revisions (0)

No revisions yet.