Working With The Collection Extension Methods (3 of 3)

February 9, 2017
powershell collections .net itsajoke butnotreally

Review

In Part 2, we took a peek at the Zip method.

posh> [System.Linq.Enumerable]::Zip

OverloadDefinitions
-------------------
static System.Collections.Generic.IEnumerable[TResult] Zip[TFirst, TSecond,
TResult](System.Collections.Generic.IEnumerable[TFirst] first, System.Collections.Generic.IEnumerable[TSecond] second,
System.Func[TFirst,TSecond,TResult] resultSelector)

Play That Func-y Music

You may have heard a Func in C# called an anonymous function or a lambda.

In the MSDN example, it looks like this.

var totalCommission = quarterlySales.Zip(quarterlyRate,
    (first, second) => first * second).Sum();

In C#, the Zip method is a member of a Collection and in the above example, they are calling the Zip method of quarterlySales collection and passing in the quarterlyRate collection as well as the lambda expression. The quarterlySales collection is passed to first and quarterlyRate is passed to second. In PowerShell, extension methods are not wired up as magically as they are in C# and we can only call them as static methods using the format [type]::method(). That means we have to provide three arguments for Zip and not just two as in the C# example.

I have not explored the technical differences between a Predicate and a Func in .Net. From where I sit, they both appear to be anonymous functions. However, you can’t just simply pass a ScriptBlock as a Func parameter.

$first = New-Object System.Collections.Generic.List[string]

$first.Add("Mike")
$first.Add("Bob")
$first.Add("Wendy")
$first.Add("Michele")
$first.Add("Extra")

$last = New-Object System.Collections.Generic.List[string]

$last.Add("Smith")
$last.Add("White")
$last.Add("Williams")
$last.Add("French")

[System.Linq.Enumerable]::Zip($first, $last, {param($f, $l) "$f $l"})

posh> [System.Linq.Enumerable]::Zip($first, $last, {param($f, $l) "$f $l"})
Cannot find an overload for "Zip" and the argument count: "3".
At line:1 char:1
+ [System.Linq.Enumerable]::Zip($first, $last, {param($f, $l) "$f $l"})
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodCountCouldNotFindBest

Cannot find an overload for “Zip” and the argument count: “3”

Clearly, the definition for Zip does expect three arguments, First, Second and resultSelector. What PowerShell really means is “Cannot find an OverloadDefinition that takes the types of arguments you passed. While PowerShell can cast a ScriptBlock into a Predicate for you, it cannot do the same for a Func. To get the .Net to accept the arguments, we have to statically type them ourselves.

posh> [System.Linq.Enumerable]::Zip($first, $last, [system.func[string,string,string]] {param($f, $l) "$f $l"})
Mike Smith
Bob White
Wendy Williams
Michele French

You see the last argument is still constructed like a ScriptBlock that contains a Param block defining two parameters. But, we’re telling .Net that it is a [system.func[string,string,string]] - A Func that takes 3 strings. Okay, so, you might be confused at this point. The Param block of our function is only taking in two arguments. Why [string,string,string]? If you look back at the definition it should become clear.

Func[TFirst,TSecond,TResult]

The third type is the type of the return value of your anonymous function.

You could just as easily take in two Collections of strings and return an integer.

posh> [System.Linq.Enumerable]::Zip($first, $last, [system.func[string,string,int]] {param($f, $l) $f.length + $l.length})
9
8
13
13

The Possibilities Are Enumerable

Now you should be able to look through all of the available Linq.Enumerable and understand how to construct the necessary arguments.

PowerShell (v5+) is getting extremely efficient at working with Arrays and Collections and using the build in Cmdlets with the Pipeline might still be faster - both to write and to run. But, if you have one or more Collections and you need to do some processing of the contents, it might be worth testing a Linq.Enumerable method to see which is ultimately better for you.

comments powered by Disqus