1

I’m writing a PowerShell class as follows:

class FileTransferStatus {

    $totalBytesToTransfer
    $bytesLeftToTransfer

    FileTransferStatus([System.IO.FileInfo[]]$files)
    {
        this.$totalBytesToTransfer = 0
        foreach ($file in $files) {
            this.$totalBytesToTransfer += $file.length
        }
        this.$bytesLeftToTransfer = this.$totalBytesToTransfer
    }
}

I’m getting an error on the last line of the constructor, which tries to initialize $bytesLeftToTransfer. The message is: Variable is not assigned in the method.

Anyone know what this message means, or how to fix the problem?

aksarben
  • 588
  • 3
  • 7
  • 4
    you have 4 typos: `this.$totalBytesToTransfer` should be `$this.totalBytesToTransfer` – Santiago Squarzon Jun 06 '23 at 19:16
  • Wow. I can't tell you how long I stared at that screen without noticing the misplaced dollar sign. Of course the cryptic error message didn't help much. But all's well that ends well. Thanks for the quick turnaround! – aksarben Jun 06 '23 at 21:25
  • A note to potential close-voters: While to those intimately familiar with PowerShell this can be seen as a mere _typo_, to the less initiated there are syntax issues well worth exploring, so I suggest keeping this question open. – mklement0 Jun 07 '23 at 22:23

1 Answers1

1

Santiago Squarzon has provided the crucial pointer in a comment:

  • All instances of this.$totalBytesToTransfer should be $this.totalBytesToTransfer instead.

While to users already familiar with PowerShell class your code could be seen as a mere typo, the required syntax is worth explaining:

  • The automatic $this variable refers to the instance at hand.

  • To refer to a property of the instance at hand, you must use $this combined with the usual dot notation, but without $- prefixing the property name - even though properties must be declared with that prefix; that is:

    • To declare a property named TotalBytesToTransfer, you must use variable syntax, which requires $: $TotalBytesToTransfer
      (typically declared with a type, e.g. [long] $TotalBytesToTransfer).

    • To access that property, you must use $this.TotalBytesToTransfer

The surprising aspects are:

  • Compared to C#:

    • PowerShell requires the equivalent of this - $this - to refer to instance properties, which is optional in C# (both for properties and fields; the latter have no PowerShell counterpart).
  • Compared to other parts of PowerShell:

    • Property-defining class instance variables aren't implicitly in scope as such in instance methods, whereas the loosely analogous parameter-defining variables in function and scripts are.

As for what you tried:

Anyone know what this message means?

Error message Variable is not assigned in the method. is trying to tell you that, inside a given method, you attempted to reference a variable that wasn't also defined earlier in the same method, which was triggered by the accidental reference to non-existent variable $totalBytesToTransfer.

  • As noted above, property-defining variables aren't implicitly accessible, and any variable references must be to method-local variables, i.e. to variables defined earlier in the same method.

    • The exception are variable references with an explicit scope specifier, such as $global:foo; however, such references are best avoided in classes, so as not to break encapsulation.
  • The error message - cited as of PowerShell 7.3.4 - could be improved, which is the subject of GitHub issue #19767.

The reason only the last of your four this.$totalBytesToTransfer references triggered the error is rooted in the fundamentals of PowerShell's syntax, which combines shell-like syntax with syntax familiar from traditional programming languages via distinct parsing modes (see the general information in the bottom section):

  • Perhaps surprisingly, this.$totalBytesToTransfer is valid as a verbatim command name in PowerShell, i.e. as an identifier at the start of a statement, so there's no syntax problem.

    • E.g., you can define a function literally named this.$totalBytesToTransfer and call it:

      function this.$totalBytesToTransfer { 'hi!' }
      this.$totalBytesToTransfer  # -> 'hi!'
      
  • However, when the same token is used as a command argument, $totalBytesToTransfer would get expanded (interpolated), i.e. treated as a variable reference; that is, this.$totalBytesToTransfer is implicitly treated like an expandable (double-quoted) string ("..."):

    $totalBytesToTransfer = 42
    Write-Output this.$totalBytesToTransfer # -> 'this.42'
    
  • Given the above, your this.$totalBytesToTransfer tokens are interpreted as follows:

    # Call to (hypothetical) command 'this.$totalBytesToTransfer' 
    # with arguments '=' and 0
    this.$totalBytesToTransfer = 0
    
    # Call to (hypothetical) command 'this.$totalBytesToTransfer' 
    # with arguments '+=' and the value of the .length property of the
    # object stored in variable $file.
    this.$totalBytesToTransfer += $file.length
    
    # Call to (hypothetical) command 'this.$totalBytesToTransfer' 
    # with arguments '=' and the concatenation of 'this.' and the
    # the value of a (hypothetical) $totalBytesToTransfer variable.
    this.$bytesLeftToTransfer = this.$totalBytesToTransfer
    
  • In the last statement above, the hypothetical $totalBytesToTransfer variable isn't previously defined inside the method at hand, which causes PowerShell to report the error you saw.


Generally, note that PowerShell - as both a shell and a (very capable) scripting language - of necessity needs to marry two distinct syntax worlds:

  • shell commands (commands followed by whitespace-separated arguments) vs.
  • expressions (operator-based operations and method calls), as in traditional programming languages).

This necessitates two distinct parsing modes, as described in about_Parsing.

In short: The start of each statement determines which of the two parsing mode applies, which also applies to nested statements. See this answer for background information.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Thanks so much for taking the time to give such a comprehensive answer. I especially liked your comparisons to other languages. I think my years of exposure to Java & C++ may have contributed to my using the wrong syntax. But all seems well now. Once again Thank you! – aksarben Jun 08 '23 at 03:58
  • Egg on my face. Turns out I was looking at a “comment” to my original post. That's where I saw the single up arrow & flag. It's the “answers” that have both up & down arrows & a check mark. Still learning how to use the forum after all these years. – aksarben Jun 08 '23 at 23:41
  • @aksarben: No worries at all. I'm glad we got there. – mklement0 Jun 08 '23 at 23:44