PowerShell Iterators Internals Tests

Posted by : on

Category : powershell   scripts   system


PowerShell Iterators

When doing an iteration with a ForEach loop, internally, at every loop the get_Current() is called.

See this statement:

    foreach (V v in x) {scriptblock}

Translate to this:

    E e = X.GetEnumerator();
    try {
        T o;
        while (e.MoveNext()) {
            o = e.Current;
            {scriptblock}
        }
    }
    finally {
         // Dispose e
    }

Order Execution

Suppose you want to loop in a collection, but need to initialize some data before the loop, it can be done in GetEnumerator()

Let’s create a small collection:


    Class T : Collections.IEnumerator{
        [System.Collections.IEnumerator]GetEnumerator() {
                $a = @(1,2,3)
                $a | % { Write-Host "__T::IEnumerator::GetEnumerator $_" }
                # Add Logic here...
                return $a.GetEnumerator()
            }
        [Int]$count = 0
        [Object]Clone(){ return [T]@{count=$this.count} }
        [void]Reset(){ $this.count = 0 }
        [bool]MoveNext(){ return ++$this.count -lt 4 }
        [Object]get_Current(){
            Write-Host "T::IEnumerator get_Current $($this.count)"
            return $this.count 
        }
    }

If you put a write-host in get_Current() you will get an ‘interlaced’ output.


    ForEach($i in [T]::new()){
        Write-Host "ForEach Script Loop #$i"
    }

Outputs this:

	T::IEnumerator get_Current 1
	ForEach Script Loop #1
	T::IEnumerator get_Current 2
	ForEach Script Loop #2
	T::IEnumerator get_Current 3
	ForEach Script Loop #3

Whereas looping using GetEnumerator() :

    ForEach($i in [T]::new().GetEnumerator()){
        Write-Host "ForEach Script Loop #$i"
    }

Outputs this:

	__T::IEnumerator::GetEnumerator 1
	__T::IEnumerator::GetEnumerator 2
	__T::IEnumerator::GetEnumerator 3
	ForEach Script Loop #1
	ForEach Script Loop #2
	ForEach Script Loop #3

So for data initialization before looping in said data, it can be done in GetEnumerator() . All pre-loop evaluation is be done in GetEnumerator() . If you add dynamic custom logic, do it there.


Sample Custom Collection

    class __FooBar : System.Collections.IEnumerator {
        [int]$_count = 0
        [int]$_size = 0
        [object]get_Current() {
            return $this._count
        }

        [bool]MoveNext(){ 
            $this._count++; 
            return ($this._count -lt $this._size); 
        }
        [void] Log([string]$str) {
            $s = "[__FooBar 0x{0:x}]" -f [int]$this.GetHashCode()
            Write-Host "$s" -f DarkRed -n
            Write-Host "::$str" -f DarkYellow
        }
        [void] Reset() {
            $this._size = 0
            $this._count = 0
        }

        [void] Dispose() {
            # Do nothing
        }
        __FooBar(){ 
            #$this.Log("__FooBar()")
        }
    }

    class FooBar : __FooBar, System.Collections.Generic.IEnumerator[int] {
        FooBar([int]$size){ 
            $this._size = $size
        }
        [int]get_Current() {
            return $this._count
        }
    }

Looping with


    function Test-foreach{
        $StopWatch = [system.diagnostics.stopwatch]::StartNew()
        ForEach($i in [FooBar]::new(500)){
            
        }
        $StopWatch.Stop()
        return $StopWatch.Elapsed.TotalMilliseconds
    }
    function Test-foreachobject{
        $StopWatch = [system.diagnostics.stopwatch]::StartNew()
        [FooBar]::new(500) | foreach-object {
            
        }
        $StopWatch.Stop()
        return $StopWatch.Elapsed.TotalMilliseconds
    }


    Write-Host "==================" -f DarkRed
    Write-Host "   Test-foreach   " -f DarkYellow
    Write-Host "==================" -f DarkRed
    $v1=Test-foreach
    $v2=Test-foreachobject
    "Test-foreach      `t{1:n5}ms`nTest-foreachobject`t{2:n5}ms`n------`nDiff fe/feo`t`t{3:n5}ms" -f $v0,$v1, $v2, ($v1-$v2)
    Test-foreach            15.60630ms
    Test-foreachobject      8.18050ms
    ------
    Diff fe/feo             7.42580ms

This Collection inheriting from IEnumerator and IEnumerable

        
    class __FooBar : System.Collections.IEnumerator,System.Collections.IEnumerable {
        [int]$_count = 0
        [int]$_size = 0
        [System.Collections.Generic.List[int]]$_list =  [System.Collections.Generic.List[int]]::new()
        [object]get_Current() {
            return $this._count
        }
        [System.Collections.IEnumerator]GetEnumerator() {
            return $this._list.GetEnumerator()
        }
        [bool]MoveNext(){ 
            $this._count++; 
            return ($this._count -lt $this._size); 
        }
        [void] Log([string]$str) {
            $s = "[__FooBar 0x{0:x}]" -f [int]$this.GetHashCode()
            Write-Host "$s" -f DarkRed -n
            Write-Host "::$str" -f DarkYellow
        }
        [void] Reset() {
            $this._count = 0
        }
        [void] Init([int]$size) {
            $this._size = $size
            0..$this._size | % { $this._list.Add($_) }
        }
        [void] Dispose() {
            # Do nothing
        }
        __FooBar(){
            
        }
    }

      class FooBar : __FooBar, System.Collections.Generic.IEnumerator[int] {
        FooBar([int]$size){ $_size = $size ; Write-Host "CONSTRUCTOR FooBar" ; $this.Init($size)}
        [int]get_Current() {
            return $this._count
        }
    }

Iterate with :

        $toBeIterated = [FooBar]::new(500)
        ##################################
        # Iteration Loop Model 1
        [Collections.IEnumerable]$iEnumerableOftoBeIterated = $toBeIterated

        foreach($element in $iEnumerableOftoBeIterated){
           Write-Host "Current value is $element"
        }

        $toBeIterated.Reset()

        ##################################
        # Iteration Loop Model 2

        [Collections.IEnumerator] $iEnumeratorOftoBeIterated = $toBeIterated.GetEnumerator();

        while ($iEnumeratorOftoBeIterated.MoveNext()){
            Write-Host "Current value is $($iEnumeratorOftoBeIterated.Current)"

        }    

About Guillaume Plante
Guillaume Plante

A developper with a passion for technology, music, astronomy and art. Coding range: hardware/drivers, security, ai,. c/c++, powershell

Email : guillaumeplante.qc@gmail.com

Website : https://arsscriptum.ddns.net

Useful Links