38

I am new in PowerShell but am familiar with .NET classes.

I am using System.Text.StringBuilder in PowerShell script. The script is that

Function MyStringFunc([String]$line) {
    $r = New-Object -TypeName "System.Collections.Generic.List``1[[System.String]]";
    $sb = New-Object -TypeName "System.Text.StringBuilder";

    foreach ($c in $line) {
        $sb.Append($c);
        $r.Add($sb.ToString());
    }

    return $r;
}

$line1 = "123";
$a = MyStringFunc $line1;
$a

I expected the result is

1
12
123

However the result is

                  Capacity                MaxCapacity                    Length
                  --------                -----------                    ------
                        16                 2147483647                         3
123

Did I do something wrong?

Alex Yeung
  • 2,495
  • 7
  • 31
  • 48

3 Answers3

52

Several of the methods on StringBuilder like Append IIRC, return the StringBuilder so you can call more StringBuilder methods. However the way PowerShell works is that it outputs all results (return values in the case of .NET method calls). In this case, cast the result to [void] to ignore the return value e.g.:

[void]$sb.Append($c)

Note that you don't need to end lines in ; in PowerShell. However if you put multiple commands on the same line then use ;' to separate those commands.

Keith Hill
  • 194,368
  • 42
  • 353
  • 369
  • Thanks it works. However, this behaviour is not noted in MSDN. Could I know where the official reference is? – Alex Yeung Oct 18 '11 at 02:13
  • 16
    MSDN indicates correctly that StringBuilder.Append(...) returns StringBuilder. In a language like C# this isn't a problem since you can just ignore the return value. However in PowerShell, it implicitly outputs any returned value. If you don't want the returned value output by PowerShell, *you* have to cast the expression to `[void]` (or pipe the expression to Out-Null). This is just a fundamental difference between C# and a shell-based scripting language like PowerShell. – Keith Hill Oct 18 '11 at 02:16
7

Because you say your are new in PowerShell. My answer is a bit more on the way you are writting PowerShell.

PowerShell is a script language, it's used by non developper programmers such as administrators. You are a C# developper with all your knowledge, but you can write the thing you want to write more simply. In PowerShell it exists syntax to use Lists and Hastables, try the following :

$a = @()
$a += "Bonjour"
$a += "Salut"
$a
$a | get-member
Get-Member -InputObject $a

$rwc = @{}
$rwc += @{1="Blues"}
$rwc += @{2="Blacks"}
$rwc
$rwc | get-member
Get-Member -InputObject $rwc

Here are three functions doing the same thing, I know that stringbuilders are a bit more efficient in memory, but in this case who cares.

Function MyStringFunc([String]$line)
{ 
  $r = New-Object -TypeName "System.Collections.Generic.List``1[[System.String]]"; 
  $sb = New-Object -TypeName "System.Text.StringBuilder"; 

  foreach ($c in [Char[]]$line)
  { 
    $a = $sb.Append($c); 
    $r.Add($sb.ToString()); 
  }  
  return $r; 
} 

# A more simple way
Function MoreReadableStringFunction([String]$line)
{
  $r = @() # an untyped list

  foreach ($c in [Char[]]$line)
  { 
    $a += $c; 
    $r += $a;
  } 
  return $r;
}

# More simple but not so readable
Function MoreSimpleStringFunction([String]$line)
{
  $r = @() # an untyped list

  [Char[]]$line | % {$a += $_; $r += $a} 
  return $r;
}


Clear-Host
$line1 = "123";

$t1 = MyStringFunc $line1; 
$t1

$t2 = MoreReadableStringFunction $line1
$t2

$t3 = MoreSimpleStringFunction $line1
$t3
JPBlanc
  • 70,406
  • 17
  • 130
  • 175
  • I wouldn't say who cares to the difference between using += and stringbuilder to concatenate in powershell... It's exponenentially less efficient and in stringbuilder it might take .2 seconds to do the same thing a += takes 60 seconds to do. It's a very, very huge difference even with a moderate amount of +=. With += measure-command of a 10000 line loop: TotalSeconds : 49.0461414 seconds -- the same loop with stringbuilder: TotalSeconds : 0.2216707 seconds measured with measure-command – danekan May 11 '18 at 16:20
3
  1. foreach does not iterate through characters in a string, it treats it as a single item. So that we have to cast a string to [char[]] (or use $line.GetEnumerator()).

  2. The expression $sb.Append($c) gets the builder instance. In PowerShell it gets written to the output. As far as it is unwanted we should suppress this output ($null = ... or ... > $null or ... | Out-Null).

The expected output is produced by this code:

Function MyStringFunc([String]$line) {
    $r = New-Object -TypeName "System.Collections.Generic.List``1[[System.String]]";
    $sb = New-Object -TypeName "System.Text.StringBuilder";

    foreach ($c in [char[]]$line) {
        $null = $sb.Append($c);
        $r.Add($sb.ToString());
    }

    return $r;
}

$line1 = "123";
$a = MyStringFunc $line1;
$a
Roman Kuzmin
  • 40,627
  • 11
  • 95
  • 117