Addition after comment: in the while part of my code I forgot to MoveNext. Repaired. Thanks enigma state for noticing.
So you have three sequences of different items, and you want a method, that returns a sequence containing the same indexed elements of your sequences.
This is a difficult way to say, that you want a sequence like:
A[0] / B[0] / C[0]
A[1] / B[1] / C[1]
A[2] / null / C[2]
etc.
So if one of the sequences runs out of elements, you want to continue enumerating using NULL as value
This method is very similar to Enumerable.Zip, except that you have three sequences, and you want to continue enumerating if one of the sequences is too short.
Whenever you think there is a missing LINQ method, consider writing an extension method for IEnumerable. Especially if you think you can reuse it in other situations
Creating an extension method for an IEnumerable is usually fairly simple. See extension methods demystified
I'll write a generic extension method, that has three input sequences, and a result selector to define the returned output sequence. This result selector is similar to the selector parameter in Enumerable.Select.
public static IEnumerable<TResult> ZipWithNull<T1, T2, T2, TResult>(
this IEnumerable<T1> source1,
IEnumerable<T2> source2,
IEnumerable<T3> source3,
Func<T1, T2, T3, TResult> resultSelector)
{
// get the enumerators and start enumerating until there are no more elements
IEnumerator<T1> enumerator1 = source1.GetEnumerator();
IEnumerator<T2> enumerator2 = source2.GetEnumerator();
IEnumerator<T3> enumerator3 = source3.GetEnumerator();
// check if there is at least one item available
bool t1Available = enumerator1.MoveNext();
bool t2Available = enumerator2.MoveNext();
bool t3Available = enumerator3.MoveNext();
bool anyAvailabe = t1Available || t2Available || t3Available;
while (anyAvailable)
{
// if available use the Current, else use default (= null for classes)
T1 t1 = t1Available ? enumerator1.Current ?? default(T1);
T2 t2 = t2Available ? enumerator2.Current ?? default(T2);
T3 t3 = t3Available ? enumerator3.Current ?? default(T3);
TResult result = resultSelector(t1, t2, t3);
yield return result;
t1Available = enumerator1.MoveNext();
t2Available = enumerator2.MoveNext();
t3Available = enumerator3.MoveNext();
anyAvailabe = t1Available || t2Available || t3Available;
}
}
Usage:
List<A> listA = ...
List<B> listA = ...
List<C> listA = ...
// you want a dash if the value is null
const string dash = "-";
var result = listA.ZipWithNull(listB, listC,
// parameter ResultSelector: taks one a, b, c and create the output:
(a, b, c) => new
{
Number = a?.Number.ToString() ?? dash,
Name = a?.Name ?? dash,
Casted = b?.Casted ?? dash,
Id = c?.Id.ToString() ?? dash,
IsSelect = c?.IsSelect.ToString() ?? dash,
});
Do note that this result selector can be optimized. For instance, whether parameter a equals null is checked twice. With a little more effort you can make sure that every input of the resultSelector is checked only once. For the example I chose simplicity above efficiency.
The nice thing is that you can use ZipWithNull for any sequence that you want to zip with null values as substitute. Because I created ZipWithNull as any other LINQ method, I can intertwine it with other LINQ methods:
var result = customers
.Where(customer => customer.BirthDay.Year >= 2000)
.ZipWithNull(
addresses.Where(address => address.City == "Amsterdam"),
orders.Where(order => order.Total > 1000,
(customer, address, order) => new {...});
.GroupBy(...)
.OrderByDescending(...)
// Etc();
1 john test 1 truebut not this2 Steve test 2 false? is it just by order?