Reputation: 411
Imagine that I have created a C# class that extended Collection<T>
to add additional functionality for a specific use case.
public class MyCollection<T> : Collection<T> {}
Now, if I import this class into my PowerShell script, and instantiate such an object, calling member methods of the T
object from said object will perform a sort of "magic" under-the-hood in which it calls the member method one after another for each item in the collection.
Note that in this example, the fact that I'm using my own MyCollection[String]
instead of Collection[String]
makes no difference.
Add-Type -Path ".\MyCollection.cs"
$test = New-Object -TypeName MyCollection[String]
$test.Add("one")
$test.Add("two")
$test.Add("three")
$test.Length
# 3
# 3
# 5
This behavior has more-or-less been dubbed "Member Enumeration". Go here for an explanation of what I'm talking about: https://devblogs.microsoft.com/powershell/new-v3-language-features/
Since it's not actually running the ForEach-Object cmdlet (refer to the above link), I was hoping to find what is actually going on and how, if it's possible, I could override that behavior without making a hacky workaround (like creating a wrapper method in the MyCollection class for each method in the object's class).
For example:
public class MyCollection<T> : Collection<T> {
public new void ForEach-Object(this Action action) {
DoSomethingAsync(this, action);
}
}
I'm intentionally using invalid syntax above in order to help make a point. I really have no idea if this can even be done.
Alternatively, if there is another way to achieve the same thing without doing the workaround I mentioned before, I would be interested in hearing those ideas as well.
Upvotes: 1
Views: 375
Reputation: 437628
You can not prevent PowerShell's member-access enumeration (without also making your collection non-enumerable), given that it is behavior built into .
, the member-access operator.
However, you can bypass it with extra effort when you access a member, namely via the intrinsic .psbase
property:[1]
((Get-Item /), (Get-Item /)).Name # Member(-access) enumeration
((Get-Item /), (Get-Item /)).psbase.Name # NO enumeration, only the array's
# own members.
However, if you refer to a property that exists on the collection object itself, that collection-level property is used, overriding member-access enumeration.
That is, $test.Count
- given that .Count
is an (instance) member of your collection type - would return just 3
, the number of elements in the collection.
GitHub suggestion #7445 asks for a distinct syntax (operator) for member-access enumeration, but as long as PowerShell remains committed to backward compatibility (which may be forever), the behavior of .
will not change.
[1] Note that the .psbase
property only mediates member access that is restricted to the type-native members. The value of .psbase
itself is a [pscustomobject]
instance with an ETS (Extended Type System) name of System.Management.Automation.PSMemberSet
.
Upvotes: 2
Reputation: 22158
Ultimately, all of these collection types are going to implement the IEnumerable
interface, which is using the GetEnumerator method to iterate over the object collection, whether in a .NET foreach
loop on in your PowerShell Foreach-Object
cmdlet.
Some of the concrete Collection implementations are going to allow you to supply your own GetEnumerator
implementation, while others aren't. I can't think of ANY that do allow it off the top of my head, but I know Collection<T>
does not. If you need to override this functionality, you could try shadowing GetEnumerator
with the new
keyword as you're demonstrating above, and it should work just fine. Another option is to write your own collection class that implements IEnumerable, but that sounds like a pain.
Upvotes: 1