You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Existing Issue: Search the existing issues for this repository. If there is an issue that fits your needs do not file a new one. Subscribe, react, or comment on that issue instead.
Descriptive Title: Write the title for this issue as a short synopsis. If possible, provide context. For example, "Typo in Get-Foo cmdlet" instead of "Typo."
Verify Version: If there is a mismatch between documentation and the behavior on your system, ensure that the version you are using is the same as the documentation. Check this box if they match or the issue you are reporting is not version specific.
[Management.Automation.LanguagePrimitives]::IsObjectEnumerable($q)
# True$expo= [Dynamic.ExpandoObject]::new()
$expo.a='a'; $expo.b='b'
[Management.Automation.LanguagePrimitives]::IsObjectEnumerable($expo)
# True# Use member-access enumeration to retrieve the "Value" property of the KeyValuePair elements.# "Value" is not a property of the ExpandoObject itself.$expo.Value# a# b
[Management.Automation.LanguagePrimitives]::IsObjectEnumerable(@{ a='b' })
# False
Item 2
The following statement above the first Long description example is misleading.
These [ForEach-Object/ForEach()/member-access enumeration] commands are functionally identical
In general, there are numerous differences, such as:
Input processing (in-memory collections vs object streaming).
Likelihood of member name collisions.
Wildly inconsistent behavior with dictionaries.
Output type.
Handling of missing non-method members.
Handling of a missing method or method error.
Member-access propagation to nested collections.
Member name collisions between the collection and its elements.
Regarding the Get-Service example specifically, there are not quite as many, but notable differences are nonetheless present (e.g., with member-access enumeration/ForEach(), Get-Service must run to completion, there are output differences if event* yields no results, etc).
It may be best to emphasize that member-access enumeration is purely a convenience feature and that due to its design, differences in behavior may be encountered when comparing it with alternative approaches.
Item 3
If the to-be-enumerated collection contains collections itself, member-access enumeration is applied to those collections (and so on, so forth). For example:
# $a is an array containing two elements:# A nested array -> nested array -> "bar", "baz"# "foo"$a= (, (, ('bar','baz'))),'foo'$a.ToUpper()
# BAR# BAZ# FOO# The result is a flat array of three strings.
Item 4
If enumeration is terminated, either from an object lacking an accessed method or from a method raising a terminating error, output from prior successful method calls is not returned.
classClass1 { [object] Foo() { return'Bar' } }
classClass2 { [void] Foo() { throw'Error' } }
classClass3 {}
# No issue; "Bar" output from both.
([Class1]::new(), [Class1]::new()).Foo()
# Bar# Bar# On error.# No "Bar" output from Class1.
([Class1]::new(), [Class2]::new()).Foo()
# Exception:# Line |# 2 | class Class2 { [void] Foo() { throw 'Error' } }# | ~~~~~~~~~~~~~# | Error# On missing method.# No "Bar" output from Class1.
([Class1]::new(), [Class3]::new()).Foo()
# InvalidOperation: Method invocation failed because [class3] does not contain a method named 'foo'.
This is one notable difference in behavior when compared with ForEach-Object. I think explicitly calling out that output may be lost with member-access enumeration is warranted, especially as ForEach-Object is already mentioned in the document.
# "Bar" output from Class1.
[Class1]::new(), [Class2]::new() |ForEach-Object-MemberName Foo
# Bar# ForEach-Object: Exception calling "Foo" with "0" argument(s): "Error"
Item 5
If the collection of objects contains Management.Automation.PSCustomObject instances, unexpected $null values are returned if an accessed property is missing.
$foo,$bar,$baz= [pscustomobject] @{ Foo='Foo' }, [pscustomobject] @{ Bar='Bar' },
[pscustomobject] @{ Baz='Baz' }
# At least one object has a "Foo" property, so $null values should *not* be returned.# Yet a $null value is returned, one for each custom object without "Foo".ConvertTo-Json ($foo,$bar,$baz).Foo
# [# "Foo",# null,# null# ]ConvertTo-Json ((Get-Process-Id $PID),$foo).Name
# [# "pwsh",# null# ]# No object has a "Foo" property, so a *single* $null value should be returned.# Yet multiple $null values are.ConvertTo-Json ($bar,$baz).Foo
# [# null,# null# ]
This is a longstanding issue that dates back to Windows PowerShell. I believe it's worth calling out as the behavior is quite obscure.
Replace the term "list collection" with "collection" and use [Management.Automation.LanguagePrimitives]::IsObjectEnumerable() instead of checking for IList in the existing example to demonstrate what is/isn't considered a collection.
Emphasize that member-access enumeration is purely a convenience feature. Behavior differences (some subtle) may be encountered when compared with alternative approaches. I suspect further detail is not within the scope of the document.
Perhaps mention ForEach-Object -MemberName and ForEach(propertyName)/ForEach(methodName), as syntactically, these are closer alternatives.
Mention that member-access enumeration can only operate on an in-memory collection, whereas ForEach-Object is intended for streaming/one-at-a-time processing.
Add a new example showing how member-access enumeration is propagated to nested collections.
Add to the existing warning in Long description that output from successful method calls is lost if enumeration is subsequently terminated due to a missing method/method error.
Add a warning at the bottom of the document calling out how custom object inclusion in the enumerated collection affects $null output scenarios.
The text was updated successfully, but these errors were encountered:
surfingoldelephant
changed the title
Add missing information to about_Member-Access_Enumeration on termination behavior and $null output
Misleading or missing information on member-access enumeration
Jan 1, 2025
Prerequisites
Get-Foo
cmdlet" instead of "Typo."Links
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_member-access_enumeration
Summary
about_member-access_enumeration has misleading or missing information on:
IList
-implementing collections.ForEach-Object
orForEach()
.$null
output.Details
Item 1
Throughout the document, reference is made to "list collection", including a specific check for the
IList
interface.However, collections that do not implement
IList
are still supported by member-access enumeration.If
LanguagePrimitives.IsObjectEnumerable()
reports$true
(publicly available in PS v6+), member-access enumeration is supported.Item 2
Get-Service
example specifically, there are not quite as many, but notable differences are nonetheless present (e.g., with member-access enumeration/ForEach()
,Get-Service
must run to completion, there are output differences ifevent*
yields no results, etc).Item 3
If the to-be-enumerated collection contains collections itself, member-access enumeration is applied to those collections (and so on, so forth). For example:
Item 4
If enumeration is terminated, either from an object lacking an accessed method or from a method raising a terminating error, output from prior successful method calls is not returned.
This is one notable difference in behavior when compared with
ForEach-Object
. I think explicitly calling out that output may be lost with member-access enumeration is warranted, especially asForEach-Object
is already mentioned in the document.Item 5
If the collection of objects contains
Management.Automation.PSCustomObject
instances, unexpected$null
values are returned if an accessed property is missing.This is a longstanding issue that dates back to Windows PowerShell. I believe it's worth calling out as the behavior is quite obscure.
Suggested Fix
In about_member-access_enumeration:
[Management.Automation.LanguagePrimitives]::IsObjectEnumerable()
instead of checking forIList
in the existing example to demonstrate what is/isn't considered a collection.ForEach-Object -MemberName
andForEach(propertyName)
/ForEach(methodName)
, as syntactically, these are closer alternatives.ForEach-Object
is intended for streaming/one-at-a-time processing.$null
output scenarios.The text was updated successfully, but these errors were encountered: