Search code examples
c#powershellpowershell-3.0

How to override the "member enumeration" behavior of Collection objects in PowerShell


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.


Solution

  • 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.