Sunday, May 28, 2023

PowerShell: StringBuild AppendLine lessons from C

Two years ago I wrote a post, PowerShell: Inadvertently Returning Multiple Values from a Function and low and behold I found a found a common C# data type that is a common culprit of this issue, StringBuilder. I have coded C# for twenty-tree years and I did not realize the each Append* method of StringBuilder returns a reference to the StringBuilder.

To demonstrate consider this C# snippet:

var builder = new StringBuilder();

builder.AppendLine("Environment Properties:");
builder.AppendLine($"MachineName: {Environment.MachineName}");
builder.AppendLine($"UserName: {Environment.UserName}");
builder.AppendLine($"UserDomainName: {Environment.UserDomainName}");
builder.AppendLine($"OSVersion: {Environment.OSVersion}");
builder.AppendLine($"ProcessorCount: {Environment.ProcessorCount}");
builder.AppendLine(
  $"Is64BitOperatingSystem: {Environment.Is64BitOperatingSystem}");
builder.AppendLine(
  $"SystemDirectory: {Environment.SystemDirectory}");
builder.AppendLine($"CurrentDirectory: {Environment.CurrentDirectory}");

Console.Write(builder.ToString());

In the documentation for the AppendLine method, AppendLine(String), the return value of AppendLine and each Append* method of StringBuilder is defined as follows:


A clearer way to write the above code in C# would be acknowledge the return value and to ignore it:

var builder = new StringBuilder();

_ = builder.AppendLine("Environment Properties:");
_ = builder.AppendLine($"MachineName: {Environment.MachineName}");
_ = builder.AppendLine($"UserName: {Environment.UserName}");
_ = builder.AppendLine(
      $"UserDomainName: {Environment.UserDomainName}");
_ = builder.AppendLine($"OSVersion: {Environment.OSVersion}");
_ = builder.AppendLine(
      $"ProcessorCount: {Environment.ProcessorCount}");
_ = builder.AppendLine(
      $"Is64BitOperatingSystem: {Environment.Is64BitOperatingSystem}");
_ = builder.AppendLine(
      $"SystemDirectory: {Environment.SystemDirectory}");
_ = builder.AppendLine($"CurrentDirectory: {Environment.CurrentDirectory}");

Console.Write(builder.ToString());

The following code shows PowerShell invoking AppendLine multiple times:

function Get-EnvironmentProperties {
    [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new()

    $builder.AppendLine("Environment Properties:")
    $builder.AppendLine("MachineName: " + [Environment]::MachineName)
    $builder.AppendLine("UserName: " + [Environment]::UserName)
    $builder.AppendLine("UserDomainName: " + [Environment]::UserDomainName)
    $builder.AppendLine("OSVersion: " + [Environment]::OSVersion)
    $builder.AppendLine("ProcessorCount: " + [Environment]::ProcessorCount)
    $builder.AppendLine("Is64BitOperatingSystem: " + [Environment]::Is64BitOperatingSystem)
    $builder.AppendLine("SystemDirectory: " + [Environment]::SystemDirectory)
    $builder.AppendLine("CurrentDirectory: " + [Environment]::CurrentDirectory)

    return $builder.ToString()
}

$result = Get-EnvironmentProperties

Although it appears that the PowerShell function, Get-EnvironmentProperties, returns a string. Result (the return value from Get-EnvironmentProperties) in an array of 10 elements:


The method AppendLine is invoked nine times so the first nine elements of the array. The tenth element of the array (index of 9) is the string return in the last line of function, Get-EnvironmentProperties.


Below show a variant of the EnvironmentProperties function suppresses the return value from StringBuilder's AppendLine:

function Get-EnvironmentProperties {
    [System.Text.StringBuilder] $builder = `
          [System.Text.StringBuilder]::new()

    $builder.AppendLine("Environment Properties:") | Out-Null
    [void]$builder.AppendLine("MachineName: " + 
              [Environment]::MachineName)
    $builder.AppendLine("UserName: " + 
              [Environment]::UserName) > $null
    $null = $builder.AppendLine("UserDomainName: " + 
              [Environment]::UserDomainName)

    return $builder.ToString()
}

Suppressing the StringBuilder returned by AppendLine results in the the correct behavior, the lone return value is as string as is show below:


A variety of mechanism were show to suppress return value of AppendLine. From the performance stand point, Out-Null is the slowest but from a readability stand point, it is the most readable for all levels of PowerShell developer.

In my code I used the following approach as I learned C as my first programming language:

    [void]$builder.AppendLine("MachineName: " + 
              [Environment]::MachineName)

With regard to performance and suppressing the result of a method/expression StackOverflow has an excellent post on the topic What's the better (cleaner) way to ignore output in PowerShell? A response by JasonMArcher demonstrates and Out-Null has the worst performance.



No comments :

Post a Comment