on 21-Jan-2009 08:15
Welcome to this addition of the PowerShell ABC's where you'll find 26 posts detailing a component of the PowerShell scripting language, one letter at a time. For today's letter of "U" I will talk about PowerShell's automatic unraveling of collections.
To unravel is to "separate or disentangle the threads" or "to free from complication or difficulty; make plain or clear".
One problem that people run into with .NET methods that return enumerators is that PowerShell will unravel the enumerator by default. By this I mean that it will "take it apart" and put it in a format that is more understandable to the user.
The foreach statement can be used to iterate over enumerable objects which typically includes anything that implements the .NET IEnumerable interface. But, PowerShell is not strict on that. There are some classes that PowerShell does not consider enumerable such as strings, dictionaries, or hashtables. The reason for this is obvious after you think of it. Consider a string for instance. In most cases, you wouldn't want all strings to suddenly be converted into a stream of characters. Or for a hash table, you wouldn't likely want that to be auto-converted into a sequence of key/value pairs. In most cases you would want these types to be treated as lightweight scalar objects.
Now, that's not saying that you can't get coerce PowerShell into treating those types as enumerators. The GetEnumerator() method can be used to extract the IEnumerable interface on an object and allow you to iterate through it's pieces.
PS C:\> foreach ($c in "abc") { $c }
abc
PS C:\> foreach ($c in "abc".GetEnumerator()) { $c }
a
b
c
But automatic unraveling can be useful when you are iterating over collections. Consider the following example:
PS C:\> foreach ($c in ("a","b","c")) { $c }
a
b
c
PS C:\> foreach ($c in ("a","b","c").GetEnumerator()) { $c }
a
b
c
This may be a bit counter-intuitive for .NET programmers as common practice is to use the Open() method to get an enumerator, process the enumerator, and then call the Close() method. PowerShell will see the enumerator returned from the Open() method and process it immediately.
Automatic unraveling can also be confusing in the context of returning an object from a function call. If you would like to return an enumerable object from a function or script, you'll want to wrap that object in an array using the unary comma operator.
PS C:\> return 1,2,3,4 | Get-Member
TypeName: System.Int32
Name MemberType Definition
---- ---------- ----------
CompareTo Method System.Int32 CompareTo(Int32 value), System.Int32 CompareTo(Object value)
Equals Method System.Boolean Equals(Object obj), System.Boolean Equals(Int32 obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
GetTypeCode Method System.TypeCode GetTypeCode()
ToString Method System.String ToString(), System.String ToString(IFormatProvider provider),...
PS C:\> return ,(1,2,3,4) | Get-Member
TypeName: System.Object[]
Name MemberType Definition
---- ---------- ----------
Count AliasProperty Count = Length
Address Method System.Object& Address(Int32 )
Clone Method System.Object Clone()
CopyTo Method System.Void CopyTo(Array array, Int32 index), System.Void CopyTo(Array array, Int64 index)
Equals Method System.Boolean Equals(Object obj)
Get Method System.Object Get(Int32 )
GetEnumerator Method System.Collections.IEnumerator GetEnumerator()
GetHashCode Method System.Int32 GetHashCode()
GetLength Method System.Int32 GetLength(Int32 dimension)
GetLongLength Method System.Int64 GetLongLength(Int32 dimension)
GetLowerBound Method System.Int32 GetLowerBound(Int32 dimension)
GetType Method System.Type GetType()
GetUpperBound Method System.Int32 GetUpperBound(Int32 dimension)
GetValue Method System.Object GetValue(Params Int32[] indices), System.Object GetValue(Int32 index), Sy...
Initialize Method System.Void Initialize()
Set Method System.Void Set(Int32 , Object )
SetValue Method System.Void SetValue(Object value, Int32 index), System.Void SetValue(Object value, Int...
ToString Method System.String ToString()
IsFixedSize Property System.Boolean IsFixedSize {get;}
IsReadOnly Property System.Boolean IsReadOnly {get;}
IsSynchronized Property System.Boolean IsSynchronized {get;}
Length Property System.Int32 Length {get;}
LongLength Property System.Int64 LongLength {get;}
Rank Property System.Int32 Rank {get;}
SyncRoot Property System.Object SyncRoot {get;}