Array or no array in Powershell? Why using @(…) is a good idea.

Here’s a funny thing in Powershell:

PS C:\> cd C:\WINDOWS\system32
PS C:\WINDOWS\system32> (dir *.dll).GetType()

IsPublic IsSerial Name                           BaseType
-------- -------- ----                           --------
True     True     Object[]                       System.Array

PS C:\WINDOWS\system32> (dir kernel32.dll).GetType()

IsPublic IsSerial Name                           BaseType
-------- -------- ----                           --------
True     True     FileInfo                       System.IO.FileSystemInfo

PS C:\WINDOWS\system32>

See what’s happening? If dir (or actually Get-ChildItem) finds several items matching our criteria then it returns an array, which is what you’d expect. But if the cmdlet finds just a single item then that item is returned directly instead of being encapsulated into an array. If you’re unexperienced with Powershell then that’s probably not what you’re expecting.

For example, this script will not always work as expected:

if ((dir *.dll).Length -gt 1) { 'many items' }

If there is more than 1 matching item then all will be fine, but if exactly one item is found then that script will break. And worse, it will break without the user noticing since no exception is thrown. It will simply print out ‘many items’ in this case, even though there is just one item. To understand why, try this:

PS C:\WINDOWS\system32> (dir kernel32.dll).Length

What happens is that dir returns one item, of type FileInfo, which happens to have a property called Length which contains the size of the file. Thus we’re calling System.IO.FileInfo.Length instead of System.Array.Length… Very confusing!

Fortunately, there’s an easy way to make sure that you always get an array, namely the @(…) construct which does nothing if its expression evaluates to an array and if it doesn’t, encapsulates it into one.


PS C:\WINDOWS\system32> @(dir kernel32.dll).Length
PS C:\WINDOWS\system32> if (@(dir *.dll).Length -gt 1) { 'many items' }

To sum it up: Always use @(…) if you want to work with arrays! Note that this applies to all cmdlets, not just Get-Childitem!


Extracting the base character from a character marked with a diacritic

Ever wondered how you can translate European national characters with diacritics such as å, ä, ö, ó etc into their base characters (a and o)? This can be useful for example when constructing filenames from user-given strings.

This is one way to do it:

string s = "ö";
string normalizedString = s.Normalize(NormalizationForm.FormD);

Console.WriteLine("Composite character: " + s[0]);
if (normalizedString.Length > 1)
   Console.WriteLine("Base character: " + normalizedString[0]);
   Console.WriteLine("Diacritic character: " + normalizedString[1]);

The result will be:

Composite character: ö
Base character: o
Diacritic character: ¨

Obviously, the key is the String.Normalize function. When we pass it NormalizationForm.FormD as the requested Unicode normalization form, it will separate all composite characters into their constituents. If the char is not a composite, then nothing will happen to it.

Note that the resulting string will be longer that the original if characters were separated. If needed it’s easy to iterate over the characters to filter our non-letters using conditions such as

if (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.LowercaseLetter ||
    CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.UppercaseLetter)

Happy character-mangling!

Signalling events from a class

For easy reference, here’s how to create an event in a class that listeners can react to:

class DeleteSubOrderEventArgs : EventArgs
    public int SubOrderId;
    public DeleteSubOrderEventArgs(int subOrderId)
        SubOrderId = subOrderId;

class Foobar
    public delegate void DeleteEventDelegate(object sender, DeleteSubOrderEventArgs e);
    public event DeleteEventDelegate DeleteSubOrderClick;
    protected void DeleteButton_Click(object sender, ImageClickEventArgs e)
        // Signal to listeners
        if (DeleteSubOrderClick != null)
            DeleteSubOrderClick(this, new DeleteSubOrderEventArgs(SubOrder.SubOrderId));