LINQ Short Takes – Number 4 –Make Union into a UnionAll

Introduction

One thing that has exasperated and puzzled me for some time is the absence of a UnionAll from the Linq default implementation. Granted pure relational algebra that was the basis of the initial SQL implementations only defined Union. Now modern SQL implementations have had the UnionAll variant added. SQL Server has the Union All (see: UNION (Transact-SQL)). Why the designers and implementers of Linq ignored the UnionAll variant remains a mystery. Maybe it is because it is relatively easy to make Union implementation operate in the way UnionAll operates.

The way to get the Union extension method to operate like a UnionAll is to provide an implementation of this interface that says ‘everything is different’. This causes the distinct phase of the Union to keep everything, rather than default behaviour of throwing way the second, and subsequent, of any duplicate.

There is not much to the implementation. Simply explained, the understanding that you want nothing to be equal, and hence have all inputs in the output is the key to this implementation. The Equals method returning false in all cases achieves this requirement.

The Solution – Implementation – UnionAll Extension Method

The following is the implementation of the UnionAll. It simply creates an instance of the EverythingDifferentEqualityComaperer class and calls the Linq Union extension method.

/// <summary> /// This method implements the UnionAll extension method. /// This extension method results in all of the rows in <paramref name="Source1"/> /// having all the rows in <paramref name="Source2"/> appended.<br/> /// This method makes use of the Linq extension method /// <seealso cref= /// "System.Linq.Enumerable.Union(IEnumerable , /// IEnumerable , IEqualityComparer )"/> /// with and implementation of the /// <seealso cref="System.Collections.Generic.IEqualityComparer< T>"/> /// /// /// The type of objects in both <paramref name="Source1"/> and <paramref name="Source2"/>. /// </typeparam> /// <param name="Source1"> /// The input sequence which is used as the first set of rows in the output. /// These rows are of <typeparamref name="TSource"/> type. /// </param> /// <param name="Source2"> /// The input sequence which is used as the second set of rows in the output. /// These rows are of <typeparamref name="TSource"/> type. /// </param> /// <returns>An output IEnumerable of type <typeparamref name="TSource"/> /// that contains all the rows from /// <paramref name="Source1"/> and <paramref name="Source2"/>. /// </returns> /// <remarks> /// This implementation used the Linq extension method <seealso cref= /// "System.Linq.Enumerable.Union<TSource>(IEnumerable<TSource> , /// IEnumerable<TSource> , IEqualityComparer<TSource> )"/> /// with and implementation of the /// <seealso cref="System.Collections.Generic.IEqualityComparer< T>"/> /// that makes all objects not equal. This forces the distinct phase of /// the union process to maintain all of the rows in the inputs. /// </remarks> public static IEnumerable<TSource> UnionAll<TSource>(
this IEnumerable<TSource> Source1,
IEnumerable<TSource> Source2)
{
EverythingDifferentEqualityComaperer AllDifferent =
new EverythingDifferentEqualityComaperer<TSource>( );
return Source1.Union(Source2, AllDifferent);
}

The Source Code

The following URLS contain the source code presented above. The UnionAll code needs to be in a class with the following attributes (the class name is something you can choose):

Why It Works?

The supplying an object that implements the IEqualityComparer Interface allows you to control the way that the distinct phase of the standard (built-in into the .Net Framework [see MSDN documentation: Overview of the .NET Framework]) Union Linq Extension Method (see the MSDN Documentation: Enumerable.Union(TSource) Method (IEnumerable(TSource), IEnumerable(TSource), IEqualityComparer(TSource)) (System.Linq)) operates. The way, I have found, to make the Union extension method to operate like a UnionAll is to provide an implementation of this interface that says ‘everything is different’. The ‘everything is different’ implementation of IEqualityComparer causes the distinct phase of the standard Union extension method to keep all input rows from both. This has the effect of overriding the default behaviour of the Union extension method. The default behaviour of the Union extension method is to keep only the distinct values from the input sources.

I suspect that the intention of the designers, and implementers, of the Linq extensions to the .Net Framework had a very different intention for the usage of the IEqualityComparer argument to the Union extension method. That intention, I speculate, would have been to allow users to implement the semantic meaning of equality for user-defined objects. The use of IEqualityComparer argument’s implementation to ‘switch off’ the distinct process in the Union extension method would, I speculate, come as a bit of a surprise to them.

Future Versions

The UnionAll implementation, for me now, is all that I want. Hence, I do not foresee making any changes or enhancements to the implementation. My satisfaction with the implementation is not entirely complete though, I may include some argument null checking later. I will leave that for my experimentation with the Unit Testing Framework though.

This implementation does highlight, for me at least, another gap in the standard Linq implementation. This gap is an extension method that appends a single value to an existing IEnumerable, would seem like a natural next step.

Conclusions

The implementation of the UnionAll extension method demonstrates that some of the standard Linq extension methods are very flexible. The flexibility of standard extension methods allows bending them into implementations of new transformations that are new, and very useful.

Luke,
You are quite correct, Enumerable.Concat() does exactly this. The next post, which I am still writing, will cover this in its conclusions.
I thought about retracting the post, or editing it, but decided against those options. The post does show something interesting, playing with the compare function to achieve a different result.
Craig