Strange behavior for PowerShell Length after ForEach method

.Length and .Count are aliases of one another and are so-called intrinsic members (properties) that PowerShell adds to most objects, in the interest of unified treatment of scalars and collections (see this answer for background).

The exception are objects of types that PowerShell treats as collections, i.e. objects that it automatically enumerates in the pipeline, which includes instances of type [System.Collections.ObjectModel.Collection[psobject]] that the .ForEach() array method returns.

PowerShell does not add .Length and .Count properties to instances of what it considers collections, because it assumes that they have such properties themselves, type-natively, assumed to be implemented efficiently (rather than PowerShell having to perform the enumeration and counting the elements).

This is a reasonable assumption that typically holds for .Count, but rarely for .Length (arrays (System.Array) being the one notable example where .Length exists and returns the element count).

Thus, it’s generally better to use .Count rather than .Length.


When you access .Length on an instance of a type that PowerShell considers a collection, yet that type has no such type-native property, PowerShell performs member-access enumeration:

  • That is, it enumerates the collection and returns the elements.Length property values.

  • In your case, since the elements are scalars (single [int] objects), they do have the intrinsic .Length property, which in the case of a scalar always returns 1.

Therefore:

(1..3).ForEach({$_}).Length

is effectively the same as:

(1..3).ForEach({ $_.Length })

The above explains the (1, 1, 1) output.
By contrast, the [System.Collections.ObjectModel.Collection[psobject]] does have a type-native .Count property, which returns the count of elements, as expected.


Background information:

Note:

  • Only those types that PowerShell does not consider collections get the intrinsic .Count and .Length properties.

  • Conversely, types that are considered collections may or may not have any, either, or both such properties type-natively, and in the absence of a type-native property member-access enumeration is performed instead.

    • Notable examples of types that have neither property type-natively are lazy enumerables; e.g., [Linq.Enumerable]::Range(1,3).Count yields 1, 1, 1.

Which .NET types PowerShell considers collections (enumerables) and therefore automatically enumerates in the pipeline:

  • Any type that implements the IEnumerable / IEnumerable`1 .NET interface(s)

    • Except the following, which are not automatically enumerated:

      • IDictionary / IDictionary`2, which notably includes [hashtable] (@{ ... }, as a literal) and its ordered cousin, OrderedDictionary ([ordered] @{ ... }, as a literal)

      • XmlNode, the base type for [xml] (XmlDocument)

      • Strings (String); note that 'foo'.Count is 1 (the intrinsic property reflecting that a string is considered a scalar (single thing)), whereas 'foo'.Length is 3 (the type-native property that returns the character count).

  • Additionally, System.Data.DataTable (which itself doesn’t implement IEnumerable, only its .Rows property does).

Leave a Comment