What is the use of Enumerable.Zip extension method in Linq?
The Zip operator merges the corresponding elements of two sequences using a specified selector function.
var letters= new string[] { "A", "B", "C", "D", "E" };
var numbers= new int[] { 1, 2, 3 };
var q = letters.Zip(numbers, (l, n) => l + n.ToString());
foreach (var s in q)
Console.WriteLine(s);
Ouput
A1
B2
C3
C# LINQ zip function equivalent in query expression
Unfortunately the query syntax doesn't support everything that is available through the method syntax (e.g. Zip()
method, self-written extension methods or 3rd party libraries like MoreLinq).
Due to this limitation it is not possible to get this functionality into C# by the user. Instead Microsoft as owner of the language had to implement such an functionality like it is described here.
LINQ Zip all elements
Well you can create your custom Zip
extension method:
static IEnumerable<T> Zip<T1, T2, T>(this IEnumerable<T1> first,
IEnumerable<T2> second, Func<T1, T2, T> operation)
{
using (var iter1 = first.GetEnumerator())
using (var iter2 = second.GetEnumerator())
{
while (iter1.MoveNext())
{
if (iter2.MoveNext())
{
yield return operation(iter1.Current, iter2.Current);
}
else
{
yield return operation(iter1.Current, default(T2));
}
}
while (iter2.MoveNext())
{
yield return operation(default(T1), iter2.Current);
}
}
}
The main idea was taken from this post answer. You can test it in dotnetfiddle if you want
How Enumerable.Zip works with LinQ
I think you are looking for SelectMany
:
List<int> listToReturn= totals = Totals.Where(x => x.Key == myGuid)
.SelectMany(s => s.Value)
.ToList();
What you are trying to do is flatten all the lists into one sequence and that is what SelectMany
extension method do. Zip
, in the other side, merges each element of the first sequence with an element that has the same index in the second sequence, in other words, it allows you to project from two sequences of the same length.
Update
Now I understand what you are trying to achieve:
var lists= Totals.Where(x => x.Key == myGuid).Select(e=>Value).ToList();// Select the lists
if(lists.Count>0)
var result= Enumerable.Range(0, lists[0].Count).Select(i=> lists.Sum(l=>l[i]));
The thing with this solution you need to make sure that all the selected lists have the same size. I'm pretty sure there is a more elegant solution but this was that come to my mind now. If I think in something better I will update my answer
Enumerable.Zip to enforce same lengths
I would probably just reimplement Zip
the way you want to. It's really pretty simple - the following is trivially adapted from MoreLINQ. You'll want to give it a better name, mind you...
public static IEnumerable<TResult> ZipForceEqual<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (resultSelector == null) throw new ArgumentNullException("resultSelector");
return ZipForceEqualImpl(first, second, resultSelector);
}
static IEnumerable<TResult> ZipForceEqualImpl<TFirst, TSecond, TResult>(
IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (var e1 = first.GetEnumerator())
using (var e2 = second.GetEnumerator())
{
while (e1.MoveNext())
{
if (e2.MoveNext())
{
yield return resultSelector(e1.Current, e2.Current);
}
else
{
throw new InvalidOperationException("Sequences differed in length");
}
}
if (e2.MoveNext())
{
throw new InvalidOperationException("Sequences differed in length");
}
}
}
Enumerable.Zip alternative solution in Dot Net 3.0
As you know it is not available in .NET 3.5 and older version of it. However there is an implementation of it by Eric Lippert here https://blogs.msdn.microsoft.com/ericlippert/2009/05/07/zip-me-up/:
The code to do so is pretty trivial; if you happen to need this in C# 3.0, I’ve put the source code below.
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>
(this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (resultSelector == null) throw new ArgumentNullException("resultSelector");
return ZipIterator(first, second, resultSelector);
}
private static IEnumerable<TResult> ZipIterator<TFirst, TSecond, TResult>
(IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
while (e1.MoveNext() && e2.MoveNext())
yield return resultSelector(e1.Current, e2.Current);
}
Update class property inside Enumerable.Zip linq c#
Enumerable.Repeat(a, b)
generates a sequence that contains b
times a
. So you get a sequence that contains three times the same instance (you only instantiate one new MyClass()
).
So by the last execution of a.Value = b
you set the Value
property of your single MyClass
instance to 3
, and the x
in your WriteLine
is always the same instance, containing Value = 3
.
A better way to create myclasses
would be
var myclasses = Enumerable.Range(1, numbers.Count).Select(i => new MyClass());
Note that it's often a bad idea to create sequences with side effects. You actually don't use the sequence returned by Zip
but use this query only for it's side effects inside your lambda.
I guess in your real application the creation of myclasses
and the update via Zip
is more separated than in your question. But if not, you could reduce it all to this:
var myclasses = numbers.Select(i => new MyClass { Value = i });
UPDATE
The reason why you now only get 0
s as output is the deferred exeuction (again: using functional programming methods like linq only for its side effects is tricky).
The immediate solution is to add ToList
to the creation:
var myclasses = Enumerable.Range(1, numbers.Count).Select(i => new MyClass()).ToList();
Without that, the classes are instantiated only at this line:
myclasses.Zip(numbers, (a, b) => a.Value = b).ToList();
And the created classes are returned as a sequence which is then turned in to a List
by ToList()
. But you don't use that return value.
So in the next line
myclasses.ToList().ForEach(x => Console.WriteLine(x.Value));
you creat new instances of the classes.
myclasses
as you defined it is only a query, not the answer to that query.
So my final proposal:
var myclasses = Enumerable.Range(1, numbers.Count).Select(i => new MyClass());
var updatedClasses = myclasses.Zip(numbers, (a, b) => {a.Value = b; return a;}).ToList();
updatedClasses.ForEach(x => Console.WriteLine(x.Value));
Note that I changed the lambda in Zip
to return the instance of the class instead of the int
.
Why is the 2-argument overload of Enumerable.Zip() in .NET Core but not in .NET Standard?
.NET Standard is a common library that is supported by both .NET Framework and .NET Core.
Enumerable.Zip
only has one overload in .NET Framework, and in fact, the second overload was only introduced in .NET Core 3.0.
.NET Framework isn't actively being updated by Microsoft any more, hence the difference.
Related Topics
Where Is the Wpf Timer Control
Redirect from Action Filter Attribute
C# .Equals(), .Referenceequals() and == Operator
How to Quickly Check If Two Data Transfer Objects Have Equal Properties in C#
"Open/Close" SQLconnection or Keep Open
Differencebetween Directory.Enumeratefiles VS Directory.Getfiles
Regex to Strip Line Comments from C#
An Attempt Was Made to Access a Socket in a Way Forbidden by Its Access Permissions. Why
Setting Canvas Properties in an Itemscontrol Datatemplate
Determine If a Sequence Contains All Elements of Another Sequence Using Linq
Entitytype Has No Key Defined Error
How to Get the Last Day of a Month
Preprocessor Directives in Razor
String = String + Int: What's Behind the Scenes
How to Make Multi-Language App in Winforms
Timeouts with Long Running ASP.NET MVC Core Controller Httppost Method