Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Last active January 25, 2021 07:37
Show Gist options
  • Save Jaykul/85f0c89d14326d5d790281f957fba819 to your computer and use it in GitHub Desktop.
Save Jaykul/85f0c89d14326d5d790281f957fba819 to your computer and use it in GitHub Desktop.
Methods without parenthesis is a language feature, not a where/foreach thing.

There have been some twitter converstations about the Where and ForEach methods

It's like someone suddenly noticed they can be called without parenthesis, and when you do so, they look even more like the Where-Object and ForEach-Object cmdlets. I don't personally understand the problem with that -- they are, after all, exactly like those cmdlets, except for three things:

  1. The method is blocking (vs. streaming)
  2. The where method has some optional additional parameters
  3. The methods are very fast

Of course, teaching these methods is complicated, because they don't show up in Get-Member, and they're virtually undocumented. In fact, it could be argued that you shouldn't use them or teach them at all. Except, did I mention they're blazingly fast?

PS C:\> (measure-command { (1..100000)|Where{ $_ % 50 -eq 0 } }).ToString()
00:00:01.0668781
PS C:\> (measure-command { (1..100000).Where{ $_ % 50 -eq 0 } }).ToString()
00:00:00.2610790

So, in an attempt to avoid the parenthesis issue, I just wanted to point out that this isn't some sort of special syntax for .Where and .ForEach -- In PowerShell you never need parenthesis to call any method which takes a single scriptblock as it's argument.

This is normal PowerShell syntax for scriptblock extension methods.

# It's true that we can call the magic Where and ForEach functions without parenthesis:
(1..100).Where{ $_ % 5 -eq 0 }
(1..5).ForEach{ $_ * $_ }

# In fact, that syntax extends to ALL METHODS that take a scriptblock as the only (mandatory) parameter:
# Imagine we have an object
$o = [PSCustomObject]@{Name="Joel Bennett"; Alias="Jaykul"}
# And this object has a method which takes a scriptblock:
$o | Add-Member ScriptMethod Greet {
        param([ScriptBlock]$code)
        $greeting = if($code) {
            $code.Invoke($this)
        } else {
            "Hello"
        }
        "{0} from {1}" -f $greeting, $this.Alias
    }

# You can call this method with parenthesis:
$o.Greet()  # returns "Hello from Jaykul"
$o.Greet({"Greetings"}) # returns "Greetings from Jaykul" 

# Or without:
$o.Greet{"Hello World"}  #returns "Hello World from Jaykul"

P.S. As far as documentation for .Where and .ForEach, personally, I rely on the error messages:

PS C:\> (1..100).where()
Cannot find an overload for ".Where({ expression } [, mode [, numberToReturn]])" and the argument count: "0".
PS C:\> (1..100).foreach()
Cannot find an overload for ".ForEach(expression [, arguments...])" and the argument count: "0"
@juneb
Copy link

juneb commented Apr 26, 2016

Was a blog post (https://www.sapien.com/blog/2016/04/22/calling-a-method-without-parentheses/) that included an explanation that it was general and not limited to Where and ForEach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment