Kleine Tipps für Zwischendurch (Teil 15) – der nicht gerade triviale Umgang mit generischen Collections

Die PowerShell besitzt leider ihre Bereiche, die für „Normalsterbliche“ nur schwer nachvollziehbar sind (oder sein dürften), da sie einfach gute bis sehr gute Kenntnisse über die .NET-Programmierung voraussetzen. Der positive Aspekt ist, dass auch der „PowerUser“ darüber im Allgemeinen nichts wissen muss, aber ein wenig störend ist es schon wenn man hin und wieder mit Details konfrontiert wird, von denen man nicht die leiseste Ahnung hat was sie bedeuten (auf der andern Seite hat das auch seinen Reiz).

Hier ein kleines Beispiel. Auch ein Modul wird bei der PowerShell (natürlich) durch ein Objekt repräsentiert  – in diesem Fall ist es ein Objekt vom Typ PSModuleInfo. Das Objekt besitzt u.a. eine Property ExportedFormatFiles, die für den oder die Pfade von Typeninformationsdateien (Erweiterung .Ps1Xml) steht. Beim BitsTransfer-Modul wird hier z.B. definiert, wie ein BitsJob-Objekt formatiert ausgegeben wird. Ein

Get-Module BitsTransfer).ExportedFormatFiles

gibt den Pfad der Ps1Xml-Datei zurück.

Damit kann es im Allgemeinen auch bewenden lassen, zumal ein angehängtes Get-Member suggeriert, dass die Property einen String liefert. Wer sich jedoch die Mühe macht, die GetType-Methode anzuhängen, um das dahinter stehende Type-Objekt zu erhalten, das die Typ-Information repräsentiert, erhält etwas differenziertes Bild:

PS > (Get-Module BitsTransfer).ExportedFormatFiles.GetType().Name
ReadOnlyCollection`1

Es stellt sich heraus, dass die Property vom Typ ReadOnlyCollection ist, was keine Überraschung ist, denn es kann durchaus mehrere Pfade geben. Aus irgendwelchen Gründen haben sich die Entwickler des Moduls aber nicht für ein Array oder eine einfache Collection, sondern für diesen Spezialtyp entschieden. Doch was hat das `1 im Namen zu bedeuten?

Diese Notation steht für eine sog. generische Collection, bei der alle Elemente von dem Typ sind, der beim Anlegen der Collection angegeben wurde. Die Zahl gibt die Anzahl der Typ-Parameter an, die beim Anlegen der Collection übergeben werden. In diesem Fall ist es ein einzelner Parameter.

Damit wäre auch dieses lebensnotwendige Detail geklärt;)

Möchte man selber eine generische Collection anlegen, wozu es im Allgemeinen keine echte Notwendigkeit geben dürfte, wird es leider etwas komplizierter. Aus Gründen, die vermutlich nur Bruce Payette beantworten könnte, ist ein naheliegendes

$ColNeu = New-Object -Type System.Collections.Generic.List [type]“string“

nicht erlaubt (was auch an der Schreibweise liegen kann) – jedenfalls ist ein nicht gerade ermutigender „Der Typ [System.Collections.Generic.List] kann nicht gefunden werden“-Fehler die Folge, was natürlich nicht stimmen kann, da es den Typ System.Collections.Generic.List gibt (bzw. es stimmt schon, da der Typ einfach falsch geschrieben wurde).

Funktionieren tut es mit der folgenden Varianten, durch die eine generische List-Collection mit String-Elementen angelegt wird:

# Generische Collection anlegen
Clear-Variable Col, ColType
$ColBase = [System.Collections.Generic.List“1]
$ColType = $ColBase.MakeGenericType(„String“)
$Col = New-Object -Type $ColType
$Col.Add(„Pemo“)
$Col.Add(„Bart“)
$Col.Add(„Ernie“)
$Col

Dreh- und Angelpunkt, auf den man aber erst einmal kommen muss, ist das MakeGeneric-Member (der Type-Klasse), das einen generischen Typ zurückgibt, dessen konkreter Typ als Zeichenkette übergeben wird (hier wird normalerweise ein Arrray von Typnamen übergeben, aber es funktioniert auch mit einem einzelnen Namen). MakeGeneric gibt ein Type-Objekt zurück, das dem Type-Parameter des vertrauten New-Object-Cmdlets übergeben wird, um ein Objekt dieses Typs zu erhalten. Dies ist unsere generische Collection (Liste), an die per Add ein paar Werte angehängt werden.

Wer von Generics noch nicht genug hat, kann endlich auch eine ReadOnlyCollection selber anlegen, in dem ihr beim Instanzieren eine vorhandene generische Liste übergeben wird. Leicht gesagt, denn die Umsetzung ist wieder einmal recht trickreich:

$ReadOnlyColBase = [System.Collections.ObjectModel.ReadOnlyCollection“1]
$ReadOnlyColType = $ReadOnlyColBase.MakeGenericType(„String“)
$ReadOnlyCol = New-Object -Type $ReadOnlyColType @(,$Col)
$ReadOnlyCol

Der Trick besteht in diesem Fall darin zu erreichen, dass die Collection $Col beim Instanzieren der Klasse vom Typ ReadOnlyCollection als Ganzes und nicht Element für Element übergeben wird. Das lässt sich offenbar nur erreichen, in dem die Collection in Gestalt eines Array übergeben wird und das unscheinbare Komma (der Kommaoperator ist bereits etwas Gebräuchlicher) dafür sorgt, dass sie als Ganzes übergeben wird.

Muss man das als PowerShell-User im Detail verstanden haben? Ich denke nicht, zumal es dafür einfach zu wenige konkrete Anwendungsfälle gibt (im Zusammenhang mit System Center Operation Manager habe ich ein paar Beispiele gefunden, in denen diese Techniken zum Einsatz kommen, was aber für mich ein Beweis dafür ist, dass die PowerShell-Schnittstelle beim SCS einfach komfortabler sein müsste), wenngleich solche Experimente durchaus ihren Reiz haben. Ich muss allerdings zugeben, dass ich die Lösung ohne die „kollektive Intelligenz“ des Internets (sprich mit Hilfe der Suchmaschine) auch nicht so ohne weiteres gefunden hätte. 

Schreibe einen Kommentar

Bitte logge dich mit einer dieser Methoden ein, um deinen Kommentar zu veröffentlichen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s