Have you ever tried to combines sequences in order to build Cartesian products in LINQ ? This is really easily achieved using the query expressions syntax, writing for instance :
var ints = Enumerable.Range(1, 4); var longs = Enumerable.Range(1, 3).Select(i => (long)i); var products = from i in ints from l in longs select i * l;
When I write LINQ queries, I tend to use more often the fluent syntax, as it is more powerful, and allows me to use a wider range of operators. I also try not to mix both syntaxes, because I find it quite confusing. When I want to perform, inside of a more complex query, a simple cross-product as the previous one, the “fluent” equivalent syntax is the following :
var products = ints.SelectMany(i => longs.Select(l => i * l));
In fact, this syntax works very well, but I don’t find it as expressive as the first one. And if you go one step further and add a third sequence to the party…
var ints = Enumerable.Range(1, 4); var longs = Enumerable.Range(1, 3).Select(i => (long)i); var strings = new[] { "a", "b", "c" }; var classic = from i in ints from l in longs from s in strings select (i * l).ToString() + s;
The “fluent” syntax gets even more confusing (at least to me) :
var fluent = ints.SelectMany( i => longs.SelectMany( l => strings.Select(s => (i * l).ToString() + s)));
I think that what bothers me might be the ever-increasing number of unclosed parenthesis… Anyway, as usual, I played with extensions methods and here is what I came up with, using a new “Cross” method, for the 2 sequences cross-product :
var results = ints .Cross(longs) .Select((i, l) => i * l);
And the same syntax for the 3 sequences cross product :
var results = ints .Cross(longs) .Cross(strings) .Select((i, l, s) => (i * l).ToString() + s);
Don’t you find it more expressive ?
Finally, here are the extensions methods implementations :
public static IEnumerable<Tuple<TLeft, TRight>> Cross<TLeft, TRight>( this IEnumerable<TLeft> left, IEnumerable<TRight> right) { return left.SelectMany(l => right.Select(r => Tuple.Create(l, r))); } public static IEnumerable<Tuple<TLeft1, TLeft2, TRight>> Cross<TLeft1, TLeft2, TRight>( this IEnumerable<Tuple<TLeft1, TLeft2>> left, IEnumerable<TRight> right) { return left.SelectMany(l => right.Select(r => Tuple.Create(l.Item1, l.Item2, r))); } public static IEnumerable<TResult> Select<T1, T2, TResult>( this IEnumerable<Tuple<T1, T2>> source, Func<T1, T2, TResult> selector) { return source.Select(t => selector(t.Item1, t.Item2)); } public static IEnumerable<TResult> Select<T1, T2, T3, TResult>( this IEnumerable<Tuple<T1, T2, T3>> source, Func<T1, T2, T3, TResult> selector) { return source.Select(t => selector(t.Item1, t.Item2, t.Item3)); }
I must admit that those generic methods signatures are horrible, though…