about_Classes_Inheritance

Kurze Beschreibung

Beschreibt, wie Sie Klassen definieren können, die andere Typen erweitern.

Lange Beschreibung

PowerShell-Klassen unterstützen die Vererbung, mit der Sie eine untergeordnete Klasse definieren können, die das Verhalten einer übergeordneten Klasse wiederverwendet (erbt), erweitert oder ändert. Die Klasse, deren Member geerbt werden, ist die Basisklasse. Die Klasse, die die Member der Basisklasse erbt, ist die abgeleitete Klasse.

PowerShell unterstützt nur die einzelne Vererbung. Eine Klasse kann nur von einer einzelnen Klasse erben. Allerdings ist Vererbung transitiv, sodass Sie eine Vererbungshierarchie für einen Satz von Typen definieren können. Mit anderen Worten: Typ D kann vom Typ C erben, der vom Typ B erbt, der vom Basisklassentyp A erbt. Da die Vererbung transitiv ist, sind die Elemente vom Typ A für den Typ D verfügbar.

Abgeleitete Klassen erben nicht alle Member der Basisklasse. Die folgenden Member werden nicht geerbt:

  • Statische Konstruktoren, die die statischen Daten einer Klasse initialisieren.
  • Instanzkonstruktoren, die Sie aufrufen, um eine neue Instanz der Klasse zu erstellen. Jede Klasse muss ihre eigenen Konstruktoren definieren.

Sie können eine Klasse erweitern, indem Sie eine neue Klasse erstellen, die von einer vorhandenen Klasse abgeleitet wird. Die abgeleitete Klasse erbt die Eigenschaften und Methoden der Basisklasse. Sie können die Basisklassenmmber nach Bedarf hinzufügen oder außer Kraft setzen.

Klassen können auch von Schnittstellen erben, die einen Vertrag definieren. Eine Klasse, die von einer Schnittstelle erbt, muss diesen Vertrag implementieren. In diesem Fall kann die Klasse wie jede andere Klasse verwendet werden, die diese Schnittstelle implementiert. Wenn eine Klasse von einer Schnittstelle erbt, aber die Schnittstelle nicht implementiert, löst PowerShell einen Analysefehler für die Klasse aus.

Einige PowerShell-Operatoren sind von einer Klasse abhängig, die eine bestimmte Schnittstelle implementiert. Der Operator sucht beispielsweise nur auf die Referenzgleichheit, es sei denn, -eq die Klasse implementiert die System.IEquatable-Schnittstelle . Die -le, -lt, -geund -gt Operatoren funktionieren nur für Klassen, die die System.IComparable-Schnittstelle implementieren.

Eine abgeleitete Klasse verwendet die : Syntax, um eine Basisklasse zu erweitern oder Schnittstellen zu implementieren. Die abgeleitete Klasse sollte immer in der Klassendeklaration ganz links sein.

Dieses Beispiel zeigt die grundlegende PowerShell-Klassenvererbungssyntax.

Class Derived : Base {...}

Dieses Beispiel zeigt die Vererbung mit einer Schnittstellendeklaration, die nach der Basisklasse kommt.

Class Derived : Base, Interface {...}

Syntax

Die Klassenvererbung verwendet die folgenden Syntaxen:

Eine Zeilensyntax

class <derived-class-name> : <base-class-or-interface-name>[, <interface-name>...] {
    <derived-class-body>
}

Zum Beispiel:

# Base class only
class Derived : Base {...}
# Interface only
class Derived : System.IComparable {...}
# Base class and interface
class Derived : Base, System.IComparable {...}

Mehrzeilige Syntax

class <derived-class-name> : <base-class-or-interface-name>[,
    <interface-name>...] {
    <derived-class-body>
}

Zum Beispiel:

class Derived : Base,
                System.IComparable,
                System.IFormattable,
                System.IConvertible {
    # Derived class definition
}

Beispiele

Beispiel 1 : Erben und Überschreiben von einer Basisklasse

Das folgende Beispiel zeigt das Verhalten geerbter Eigenschaften mit und ohne Außerkraftsetzung. Führen Sie die Codeblöcke nach dem Lesen der Beschreibung in der reihenfolge aus.

Definieren der Basisklasse

Der erste Codeblock definiert PublishedWork als Basisklasse. Es verfügt über zwei statische Eigenschaften, List und Artists. Als Nächstes definiert sie die statische Methode, um der statischen RegisterWork()List -Eigenschaft und den Künstlern die Künstler -Eigenschaft hinzuzufügen, eine Nachricht für jeden neuen Eintrag in den Listen zu schreiben.

Die Klasse definiert drei Instanzeigenschaften, die eine veröffentlichte Arbeit beschreiben. Schließlich definiert sie die Register() Methoden und ToString() Instanzen.

class PublishedWork {
    static [PublishedWork[]] $List    = @()
    static [string[]]        $Artists = @()

    static [void] RegisterWork([PublishedWork]$Work) {
        $wName   = $Work.Name
        $wArtist = $Work.Artist
        if ($Work -notin [PublishedWork]::List) {
            Write-Verbose "Adding work '$wName' to works list"
            [PublishedWork]::List += $Work
        } else {
            Write-Verbose "Work '$wName' already registered."
        }
        if ($wArtist -notin [PublishedWork]::Artists) {
            Write-Verbose "Adding artist '$wArtist' to artists list"
            [PublishedWork]::Artists += $wArtist
        } else {
            Write-Verbose "Artist '$wArtist' already registered."
        }
    }

    static [void] ClearRegistry() {
        Write-Verbose "Clearing PublishedWork registry"
        [PublishedWork]::List    = @()
        [PublishedWork]::Artists = @()
    }

    [string] $Name
    [string] $Artist
    [string] $Category

    [void] Init([string]$WorkType) {
        if ([string]::IsNullOrEmpty($this.Category)) {
            $this.Category = "${WorkType}s"
        }
    }

    PublishedWork() {
        $WorkType = $this.GetType().FullName
        $this.Init($WorkType)
        Write-Verbose "Defined a published work of type [$WorkType]"
    }

    PublishedWork([string]$Name, [string]$Artist) {
        $WorkType    = $this.GetType().FullName
        $this.Name   = $Name
        $this.Artist = $Artist
        $this.Init($WorkType)

        Write-Verbose "Defined '$Name' by $Artist as a published work of type [$WorkType]"
    }

    PublishedWork([string]$Name, [string]$Artist, [string]$Category) {
        $WorkType    = $this.GetType().FullName
        $this.Name   = $Name
        $this.Artist = $Artist
        $this.Init($WorkType)

        Write-Verbose "Defined '$Name' by $Artist ($Category) as a published work of type [$WorkType]"
    }

    [void]   Register() { [PublishedWork]::RegisterWork($this) }
    [string] ToString() { return "$($this.Name) by $($this.Artist)" }
}

Definieren einer abgeleiteten Klasse ohne Außerkraftsetzungen

Die erste abgeleitete Klasse ist Album. Es überschreibt keine Eigenschaften oder Methoden. Sie fügt eine neue Instanzeigenschaft ( Genres) hinzu, die nicht für die Basisklasse vorhanden ist.

class Album : PublishedWork {
    [string[]] $Genres   = @()
}

Der folgende Codeblock zeigt das Verhalten der abgeleiteten Albumklasse . Zunächst wird festgelegt $VerbosePreference , dass die Nachrichten aus den Klassenmethoden an die Konsole ausgegeben werden. Sie erstellt drei Instanzen der Klasse, zeigt sie in einer Tabelle an und registriert sie dann mit der geerbten statischen RegisterWork() Methode. Anschließend wird die gleiche statische Methode für die Basisklasse direkt aufgerufen.

$VerbosePreference = 'Continue'
$Albums = @(
    [Album]@{
        Name   = 'The Dark Side of the Moon'
        Artist = 'Pink Floyd'
        Genres = 'Progressive rock', 'Psychedelic rock'
    }
    [Album]@{
        Name   = 'The Wall'
        Artist = 'Pink Floyd'
        Genres = 'Progressive rock', 'Art rock'
    }
    [Album]@{
        Name   = '36 Chambers'
        Artist = 'Wu-Tang Clan'
        Genres = 'Hip hop'
    }
)

$Albums | Format-Table
$Albums | ForEach-Object { [Album]::RegisterWork($_) }
$Albums | ForEach-Object { [PublishedWork]::RegisterWork($_) }
VERBOSE: Defined a published work of type [Album]
VERBOSE: Defined a published work of type [Album]
VERBOSE: Defined a published work of type [Album]

Genres                               Name                      Artist       Category
------                               ----                      ------       --------
{Progressive rock, Psychedelic rock} The Dark Side of the Moon Pink Floyd   Albums
{Progressive rock, Art rock}         The Wall                  Pink Floyd   Albums
{Hip hop}                            36 Chambers               Wu-Tang Clan Albums

VERBOSE: Adding work 'The Dark Side of the Moon' to works list
VERBOSE: Adding artist 'Pink Floyd' to artists list
VERBOSE: Adding work 'The Wall' to works list
VERBOSE: Artist 'Pink Floyd' already registered.
VERBOSE: Adding work '36 Chambers' to works list
VERBOSE: Adding artist 'Wu-Tang Clan' to artists list

VERBOSE: Work 'The Dark Side of the Moon' already registered.
VERBOSE: Artist 'Pink Floyd' already registered.
VERBOSE: Work 'The Wall' already registered.
VERBOSE: Artist 'Pink Floyd' already registered.
VERBOSE: Work '36 Chambers' already registered.
VERBOSE: Artist 'Wu-Tang Clan' already registered.

Beachten Sie, dass die Eigenschaft vom Standardkonstruktor der Basisklasse definiert wurde, obwohl die Album-Klasse keinen Wert für Category oder Konstruktoren definiert hat.

Im ausführlichen Messaging meldet der zweite Aufruf der RegisterWork() Methode, dass die Werke und Künstler bereits registriert sind. Obwohl der erste Aufruf RegisterWork() für die abgeleitete Albumklasse war, wurde die geerbte statische Methode von der Basis-PublishedWork-Klasse verwendet. Diese Methode hat die statischen List - und Artist-Eigenschaften für die Basisklasse aktualisiert, die von der abgeleiteten Klasse nicht überschreibt.

Der nächste Codeblock löscht die Registrierung und ruft die Register() Instanzmethode für die Album-Objekte auf.

[PublishedWork]::ClearRegistry()
$Albums.Register()
VERBOSE: Clearing PublishedWork registry

VERBOSE: Adding work 'The Dark Side of the Moon' to works list
VERBOSE: Adding artist 'Pink Floyd' to artists list
VERBOSE: Adding work 'The Wall' to works list
VERBOSE: Artist 'Pink Floyd' already registered.
VERBOSE: Adding work '36 Chambers' to works list
VERBOSE: Adding artist 'Wu-Tang Clan' to artists list

Die Instanzmethode für die Album-Objekte hat dieselbe Auswirkung wie das Aufrufen der statischen Methode für die abgeleitete oder Basisklasse.

Der folgende Codeblock vergleicht die statischen Eigenschaften für die Basisklasse und die abgeleitete Klasse, die zeigt, dass sie identisch sind.

[pscustomobject]@{
    '[PublishedWork]::List'    = [PublishedWork]::List -join ",`n"
    '[Album]::List'            = [Album]::List -join ",`n"
    '[PublishedWork]::Artists' = [PublishedWork]::Artists -join ",`n"
    '[Album]::Artists'         = [Album]::Artists -join ",`n"
    'IsSame::List'             = (
        [PublishedWork]::List.Count -eq [Album]::List.Count -and
        [PublishedWork]::List.ToString() -eq [Album]::List.ToString()
    )
    'IsSame::Artists'          = (
        [PublishedWork]::Artists.Count -eq [Album]::Artists.Count -and
        [PublishedWork]::Artists.ToString() -eq [Album]::Artists.ToString()
    )
} | Format-List
[PublishedWork]::List    : The Dark Side of the Moon by Pink Floyd,
                           The Wall by Pink Floyd,
                           36 Chambers by Wu-Tang Clan
[Album]::List            : The Dark Side of the Moon by Pink Floyd,
                           The Wall by Pink Floyd,
                           36 Chambers by Wu-Tang Clan
[PublishedWork]::Artists : Pink Floyd,
                           Wu-Tang Clan
[Album]::Artists         : Pink Floyd,
                           Wu-Tang Clan
IsSame::List             : True
IsSame::Artists          : True

Definieren einer abgeleiteten Klasse mit Außerkraftsetzungen

Der nächste Codeblock definiert die Illustration-Klasse, die von der Basis-PublishedWork-Klasse erbt. Die neue Klasse erweitert die Basisklasse durch Definieren der Medium-Instanzeigenschaft mit einem Standardwert von Unknown.

Im Gegensatz zur abgeleiteten Albumklasseüberschreibt Illustration die folgenden Eigenschaften und Methoden:

  • Es überschreibt die statische Artists-Eigenschaft . Die Definition ist identisch, aber die Illustration-Klasse deklariert sie direkt.
  • Sie überschreibt die Category-Instanzeigenschaft , wobei der Standardwert auf Illustrations.
  • Sie überschreibt die ToString() Instanzmethode, sodass die Zeichenfolgendarstellung einer Abbildung das Medium enthält, mit dem sie erstellt wurde.

Die Klasse definiert außerdem die statische RegisterIllustration() Methode, um zuerst die Basisklassenmethode RegisterWork() aufzurufen, und fügen sie dann der statischen Eigenschaft "overridden Artists " für die abgeleitete Klasse hinzu.

Schließlich überschreibt die Klasse alle drei Konstruktoren:

  1. Der Standardkonstruktor ist leer, mit Ausnahme einer ausführlichen Meldung, die angibt, dass er eine Illustration erstellt hat.
  2. Der nächste Konstruktor akzeptiert zwei Zeichenfolgenwerte für den Namen und den Künstler, der die Illustration erstellt hat. Anstatt die Logik zum Festlegen der Eigenschaften Name und Artist zu implementieren, ruft der Konstruktor den entsprechenden Konstruktor aus der Basisklasse auf.
  3. Der letzte Konstruktor akzeptiert drei Zeichenfolgenwerte für den Namen, den Künstler und das Medium der Abbildung. Beide Konstruktoren schreiben eine ausführliche Meldung, die angibt, dass sie eine Illustration erstellt haben.
class Illustration : PublishedWork {
    static [string[]] $Artists = @()

    static [void] RegisterIllustration([Illustration]$Work) {
        $wArtist = $Work.Artist

        [PublishedWork]::RegisterWork($Work)

        if ($wArtist -notin [Illustration]::Artists) {
            Write-Verbose "Adding illustrator '$wArtist' to artists list"
            [Illustration]::Artists += $wArtist
        } else {
            Write-Verbose "Illustrator '$wArtist' already registered."
        }
    }

    [string] $Category = 'Illustrations'
    [string] $Medium   = 'Unknown'

    [string] ToString() {
        return "$($this.Name) by $($this.Artist) ($($this.Medium))"
    }

    Illustration() {
        Write-Verbose 'Defined an illustration'
    }

    Illustration([string]$Name, [string]$Artist) : base($Name, $Artist) {
        Write-Verbose "Defined '$Name' by $Artist ($($this.Medium)) as an illustration"
    }

    Illustration([string]$Name, [string]$Artist, [string]$Medium) {
        $this.Name = $Name
        $this.Artist = $Artist
        $this.Medium = $Medium

        Write-Verbose "Defined '$Name' by $Artist ($Medium) as an illustration"
    }
}

Der folgende Codeblock zeigt das Verhalten der abgeleiteten Illustrationsklasse . Sie erstellt drei Instanzen der Klasse, zeigt sie in einer Tabelle an und registriert sie dann mit der geerbten statischen RegisterWork() Methode. Anschließend wird die gleiche statische Methode für die Basisklasse direkt aufgerufen. Schließlich schreibt es Nachrichten mit der Liste der registrierten Künstler für die Basisklasse und die abgeleitete Klasse.

$Illustrations = @(
    [Illustration]@{
        Name   = 'The Funny Thing'
        Artist = 'Wanda Gág'
        Medium = 'Lithography'
    }
    [Illustration]::new('Millions of Cats', 'Wanda Gág')
    [Illustration]::new(
      'The Lion and the Mouse',
      'Jerry Pinkney',
      'Watercolor'
    )
)

$Illustrations | Format-Table
$Illustrations | ForEach-Object { [Illustration]::RegisterIllustration($_) }
$Illustrations | ForEach-Object { [PublishedWork]::RegisterWork($_) }
"Published work artists: $([PublishedWork]::Artists -join ', ')"
"Illustration artists: $([Illustration]::Artists -join ', ')"
VERBOSE: Defined a published work of type [Illustration]
VERBOSE: Defined an illustration
VERBOSE: Defined 'Millions of Cats' by Wanda Gág as a published work of type [Illustration]
VERBOSE: Defined 'Millions of Cats' by Wanda Gág (Unknown) as an illustration
VERBOSE: Defined a published work of type [Illustration]
VERBOSE: Defined 'The Lion and the Mouse' by Jerry Pinkney (Watercolor) as an illustration

Category      Medium      Name                   Artist
--------      ------      ----                   ------
Illustrations Lithography The Funny Thing        Wanda Gág
Illustrations Unknown     Millions of Cats       Wanda Gág
Illustrations Watercolor  The Lion and the Mouse Jerry Pinkney

VERBOSE: Adding work 'The Funny Thing' to works list
VERBOSE: Adding artist 'Wanda Gág' to artists list
VERBOSE: Adding illustrator 'Wanda Gág' to artists list
VERBOSE: Adding work 'Millions of Cats' to works list
VERBOSE: Artist 'Wanda Gág' already registered.
VERBOSE: Illustrator 'Wanda Gág' already registered.
VERBOSE: Adding work 'The Lion and the Mouse' to works list
VERBOSE: Adding artist 'Jerry Pinkney' to artists list
VERBOSE: Adding illustrator 'Jerry Pinkney' to artists list

VERBOSE: Work 'The Funny Thing' already registered.
VERBOSE: Artist 'Wanda Gág' already registered.
VERBOSE: Work 'Millions of Cats' already registered.
VERBOSE: Artist 'Wanda Gág' already registered.
VERBOSE: Work 'The Lion and the Mouse' already registered.
VERBOSE: Artist 'Jerry Pinkney' already registered.

Published work artists: Pink Floyd, Wu-Tang Clan, Wanda Gág, Jerry Pinkney

Illustration artists: Wanda Gág, Jerry Pinkney

Das ausführliche Messaging beim Erstellen der Instanzen zeigt Folgendes:

  • Beim Erstellen der ersten Instanz wurde der Standardkonstruktor der Basisklasse vor dem abgeleiteten Klassenstandardkonstruktor aufgerufen.
  • Beim Erstellen der zweiten Instanz wurde der explizit geerbte Konstruktor für die Basisklasse vor dem abgeleiteten Klassenkonstruktor aufgerufen.
  • Beim Erstellen der dritten Instanz wurde der Standardkonstruktor der Basisklasse vor dem abgeleiteten Klassenkonstruktor aufgerufen.

Die ausführlichen Nachrichten aus der RegisterWork() Methode deuten darauf hin, dass die Werke und Künstler bereits registriert wurden. Dies liegt daran, dass die RegisterIllustration() Methode intern die RegisterWork() Methode aufgerufen hat.

Beim Vergleich des Werts der statischen Artist-Eigenschaft für die Basisklasse und die abgeleitete Klasse unterscheiden sich die Werte jedoch. Die Artists-Eigenschaft für die abgeleitete Klasse umfasst nur Illustratoren, nicht die Albumkünstler. Durch das Neudefinieren der Artist-Eigenschaft in der abgeleiteten Klasse wird verhindert, dass die Klasse die statische Eigenschaft für die Basisklasse zurückgibt.

Der letzte Codeblock ruft die ToString() Methode für die Einträge der statischen List-Eigenschaft für die Basisklasse auf.

[PublishedWork]::List | ForEach-Object -Process { $_.ToString() }
The Dark Side of the Moon by Pink Floyd
The Wall by Pink Floyd
36 Chambers by Wu-Tang Clan
The Funny Thing by Wanda Gág (Lithography)
Millions of Cats by Wanda Gág (Unknown)
The Lion and the Mouse by Jerry Pinkney (Watercolor)

Die Albuminstanzen geben nur den Namen und den Künstler in ihrer Zeichenfolge zurück. Die Illustrationsinstanzen enthielten auch das Medium in Klammern, da diese Klasse die ToString() Methode überschreibt.

Beispiel 2 : Implementieren von Schnittstellen

Das folgende Beispiel zeigt, wie eine Klasse eine oder mehrere Schnittstellen implementieren kann. Im Beispiel wird die Definition einer Temperature-Klasse erweitert, um weitere Vorgänge und Verhaltensweisen zu unterstützen.

Anfängliche Klassendefinition

Vor der Implementierung von Schnittstellen wird die Temperature-Klasse mit zwei Eigenschaften definiert: Grad und Scale. Sie definiert Konstruktoren und drei Instanzmethoden, um die Instanz als Grad einer bestimmten Skalierung zurückzugeben.

Die Klasse definiert die verfügbaren Skalierungen mit der TemperatureScale-Enumeration .

class Temperature {
    [float]            $Degrees
    [TemperatureScale] $Scale

    Temperature() {}
    Temperature([float] $Degrees)          { $this.Degrees = $Degrees }
    Temperature([TemperatureScale] $Scale) { $this.Scale = $Scale }
    Temperature([float] $Degrees, [TemperatureScale] $Scale) {
        $this.Degrees = $Degrees
        $this.Scale   = $Scale
    }

    [float] ToKelvin() {
        switch ($this.Scale) {
            Celsius    { return $this.Degrees + 273.15 }
            Fahrenheit { return ($this.Degrees + 459.67) * 5/9 }
        }
        return $this.Degrees
    }
    [float] ToCelsius() {
        switch ($this.Scale) {
            Fahrenheit { return ($this.Degrees - 32) * 5/9 }
            Kelvin     { return $this.Degrees - 273.15 }
        }
        return $this.Degrees
    }
    [float] ToFahrenheit() {
        switch ($this.Scale) {
            Celsius    { return $this.Degrees * 9/5 + 32 }
            Kelvin     { return $this.Degrees * 9/5 - 459.67 }
        }
        return $this.Degrees
    }
}

enum TemperatureScale {
    Celsius    = 0
    Fahrenheit = 1
    Kelvin     = 2
}

In dieser grundlegenden Implementierung gibt es jedoch einige Einschränkungen, wie in der folgenden Beispielausgabe gezeigt:

$Celsius    = [Temperature]::new()
$Fahrenheit = [Temperature]::new([TemperatureScale]::Fahrenheit)
$Kelvin     = [Temperature]::new(0, 'Kelvin')

$Celsius, $Fahrenheit, $Kelvin

"The temperatures are: $Celsius, $Fahrenheit, $Kelvin"

[Temperature]::new() -eq $Celsius

$Celsius -gt $Kelvin
Degrees      Scale
-------      -----
   0.00    Celsius
   0.00 Fahrenheit
   0.00     Kelvin

The temperatures are: Temperature, Temperature, Temperature

False

InvalidOperation:
Line |
  11 |  $Celsius -gt $Kelvin
     |  ~~~~~~~~~~~~~~~~~~~~
     | Cannot compare "Temperature" because it is not IComparable.

Die Ausgabe zeigt, dass Instanzen von Temperatur:

  • Wird nicht ordnungsgemäß als Zeichenfolgen angezeigt.
  • Auf Äquivalenz kann nicht ordnungsgemäß überprüft werden.
  • Kann nicht verglichen werden.

Diese drei Probleme können durch die Implementierung von Schnittstellen für die Klasse behoben werden.

Implementieren von IFormattable

Die erste Schnittstelle, die für die Temperaturklasse implementiert werden soll, ist System.IFormattable. Diese Schnittstelle ermöglicht das Formatieren einer Instanz der Klasse als verschiedene Zeichenfolgen. Um die Schnittstelle zu implementieren, muss die Klasse von System.IFormattable erben und die ToString() Instanzmethode definieren.

Die ToString() Instanzmethode muss über die folgende Signatur verfügen:

[string] ToString(
    [string]$Format,
    [System.IFormatProvider]$FormatProvider
) {
    # Implementation
}

Die Signatur, die die Schnittstelle benötigt, wird in der Referenzdokumentation aufgeführt.

Für Temperatur sollte die Klasse drei Formate unterstützen: C um die Instanz in Celsius zurückzugeben, F um sie in Fahrenheit zurückzugeben und K in Kelvin zurückzugeben. Bei jedem anderen Format sollte die Methode eine System.FormatException auslösen.

[string] ToString(
    [string]$Format,
    [System.IFormatProvider]$FormatProvider
) {
    # If format isn't specified, use the defined scale.
    if ([string]::IsNullOrEmpty($Format)) {
        $Format = switch ($this.Scale) {
            Celsius    { 'C' }
            Fahrenheit { 'F' }
            Kelvin     { 'K' }
        }
    }
    # If format provider isn't specified, use the current culture.
    if ($null -eq $FormatProvider) {
        $FormatProvider = [CultureInfo]::CurrentCulture
    }
    # Format the temperature.
    switch ($Format) {
        'C' {
            return $this.ToCelsius().ToString('F2', $FormatProvider) + '°C'
        }
        'F' {
            return $this.ToFahrenheit().ToString('F2', $FormatProvider) + '°F'
        }
        'K' {
            return $this.ToKelvin().ToString('F2', $FormatProvider) + '°K'
        }
    }
    # If we get here, the format is invalid.
    throw [System.FormatException]::new(
        "Unknown format: '$Format'. Valid Formats are 'C', 'F', and 'K'"
    )
}

In dieser Implementierung wird standardmäßig die Instanzskala für formatieren und die aktuelle Kultur beim Formatieren des numerischen Gradwerts selbst verwendet. Sie verwendet die To<Scale>() Instanzmethoden, um die Gradsätze zu konvertieren, sie an zwei Dezimalstellen zu formatieren und fügt das entsprechende Gradsymbol an die Zeichenfolge an.

Mit der implementierten erforderlichen Signatur kann die Klasse auch Überladungen definieren, damit die formatierte Instanz einfacher zurückgegeben werden kann.

[string] ToString([string]$Format) {
    return $this.ToString($Format, $null)
}

[string] ToString() {
    return $this.ToString($null, $null)
}

Der folgende Code zeigt die aktualisierte Definition für Temperatur:

class Temperature : System.IFormattable {
    [float]            $Degrees
    [TemperatureScale] $Scale

    Temperature() {}
    Temperature([float] $Degrees)          { $this.Degrees = $Degrees }
    Temperature([TemperatureScale] $Scale) { $this.Scale = $Scale }
    Temperature([float] $Degrees, [TemperatureScale] $Scale) {
        $this.Degrees = $Degrees
        $this.Scale = $Scale
    }

    [float] ToKelvin() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees + 273.15 }
            Fahrenheit { return ($this.Degrees + 459.67) * 5 / 9 }
        }
        return $this.Degrees
    }
    [float] ToCelsius() {
        switch ($this.Scale) {
            Fahrenheit { return ($this.Degrees - 32) * 5 / 9 }
            Kelvin { return $this.Degrees - 273.15 }
        }
        return $this.Degrees
    }
    [float] ToFahrenheit() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees * 9 / 5 + 32 }
            Kelvin { return $this.Degrees * 9 / 5 - 459.67 }
        }
        return $this.Degrees
    }

    [string] ToString(
        [string]$Format,
        [System.IFormatProvider]$FormatProvider
    ) {
        # If format isn't specified, use the defined scale.
        if ([string]::IsNullOrEmpty($Format)) {
            $Format = switch ($this.Scale) {
                Celsius    { 'C' }
                Fahrenheit { 'F' }
                Kelvin     { 'K' }
            }
        }
        # If format provider isn't specified, use the current culture.
        if ($null -eq $FormatProvider) {
            $FormatProvider = [CultureInfo]::CurrentCulture
        }
        # Format the temperature.
        switch ($Format) {
            'C' {
                return $this.ToCelsius().ToString('F2', $FormatProvider) + '°C'
            }
            'F' {
                return $this.ToFahrenheit().ToString('F2', $FormatProvider) + '°F'
            }
            'K' {
                return $this.ToKelvin().ToString('F2', $FormatProvider) + '°K'
            }
        }
        # If we get here, the format is invalid.
        throw [System.FormatException]::new(
            "Unknown format: '$Format'. Valid Formats are 'C', 'F', and 'K'"
        )
    }

    [string] ToString([string]$Format) {
        return $this.ToString($Format, $null)
    }

    [string] ToString() {
        return $this.ToString($null, $null)
    }
}

enum TemperatureScale {
    Celsius    = 0
    Fahrenheit = 1
    Kelvin     = 2
}

Die Ausgabe für die Methodenüberladungen wird im folgenden Block angezeigt.

$Temp = [Temperature]::new()
"The temperature is $Temp"
$Temp.ToString()
$Temp.ToString('K')
$Temp.ToString('F', $null)
The temperature is 0.00°C

0.00°C

273.15°K

32.00°F

Implementieren von IEquatable

Da die Temperature-Klasse nun zur Lesbarkeit formatiert werden kann, müssen Die Benutzer in der Lage sein, zu überprüfen, ob zwei Instanzen der Klasse gleich sind. Zur Unterstützung dieses Tests muss die Klasse die System.IEquatable-Schnittstelle implementieren.

Um die Schnittstelle zu implementieren, muss die Klasse von System.IEquatable erben und die Equals() Instanzmethode definieren. Die Equals() Methode muss über die folgende Signatur verfügen:

[bool] Equals([object]$Other) {
    # Implementation
}

Die Signatur, die die Schnittstelle benötigt, wird in der Referenzdokumentation aufgeführt.

Bei Temperatur sollte die Klasse nur den Vergleich von zwei Instanzen der Klasse unterstützen. Für einen anderen Wert oder Typ, einschließlich $null, sollte er zurückgegeben werden $false. Beim Vergleich von zwei Temperaturen sollte die Methode beide Werte in Kelvin umwandeln, da die Temperaturen sogar mit unterschiedlichen Skalierungen gleichwertig sein können.

[bool] Equals([object]$Other) {
    # If the other object is null, we can't compare it.
    if ($null -eq $Other) {
        return $false
    }

    # If the other object isn't a temperature, we can't compare it.
    $OtherTemperature = $Other -as [Temperature]
    if ($null -eq $OtherTemperature) {
        return $false
    }

    # Compare the temperatures as Kelvin.
    return $this.ToKelvin() -eq $OtherTemperature.ToKelvin()
}

Mit der implementierten Schnittstellenmethode lautet die aktualisierte Definition für Temperatur :

class Temperature : System.IFormattable, System.IEquatable[object] {
    [float]            $Degrees
    [TemperatureScale] $Scale

    Temperature() {}
    Temperature([float] $Degrees)          { $this.Degrees = $Degrees }
    Temperature([TemperatureScale] $Scale) { $this.Scale = $Scale }
    Temperature([float] $Degrees, [TemperatureScale] $Scale) {
        $this.Degrees = $Degrees
        $this.Scale = $Scale
    }

    [float] ToKelvin() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees + 273.15 }
            Fahrenheit { return ($this.Degrees + 459.67) * 5 / 9 }
        }
        return $this.Degrees
    }
    [float] ToCelsius() {
        switch ($this.Scale) {
            Fahrenheit { return ($this.Degrees - 32) * 5 / 9 }
            Kelvin { return $this.Degrees - 273.15 }
        }
        return $this.Degrees
    }
    [float] ToFahrenheit() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees * 9 / 5 + 32 }
            Kelvin { return $this.Degrees * 9 / 5 - 459.67 }
        }
        return $this.Degrees
    }

    [string] ToString(
        [string]$Format,
        [System.IFormatProvider]$FormatProvider
    ) {
        # If format isn't specified, use the defined scale.
        if ([string]::IsNullOrEmpty($Format)) {
            $Format = switch ($this.Scale) {
                Celsius    { 'C' }
                Fahrenheit { 'F' }
                Kelvin     { 'K' }
            }
        }
        # If format provider isn't specified, use the current culture.
        if ($null -eq $FormatProvider) {
            $FormatProvider = [CultureInfo]::CurrentCulture
        }
        # Format the temperature.
        switch ($Format) {
            'C' {
                return $this.ToCelsius().ToString('F2', $FormatProvider) + '°C'
            }
            'F' {
                return $this.ToFahrenheit().ToString('F2', $FormatProvider) + '°F'
            }
            'K' {
                return $this.ToKelvin().ToString('F2', $FormatProvider) + '°K'
            }
        }
        # If we get here, the format is invalid.
        throw [System.FormatException]::new(
            "Unknown format: '$Format'. Valid Formats are 'C', 'F', and 'K'"
        )
    }

    [string] ToString([string]$Format) {
        return $this.ToString($Format, $null)
    }

    [string] ToString() {
        return $this.ToString($null, $null)
    }

    [bool] Equals([object]$Other) {
        # If the other object is null, we can't compare it.
        if ($null -eq $Other) {
            return $false
        }

        # If the other object isn't a temperature, we can't compare it.
        $OtherTemperature = $Other -as [Temperature]
        if ($null -eq $OtherTemperature) {
            return $false
        }

        # Compare the temperatures as Kelvin.
        return $this.ToKelvin() -eq $OtherTemperature.ToKelvin()
    }
}

enum TemperatureScale {
    Celsius    = 0
    Fahrenheit = 1
    Kelvin     = 2
}

Der folgende Block zeigt, wie sich die aktualisierte Klasse verhält:

$Celsius    = [Temperature]::new()
$Fahrenheit = [Temperature]::new(32, 'Fahrenheit')
$Kelvin     = [Temperature]::new([TemperatureScale]::Kelvin)

@"
Temperatures are: $Celsius, $Fahrenheit, $Kelvin
`$Celsius.Equals(`$Fahrenheit) = $($Celsius.Equals($Fahrenheit))
`$Celsius -eq `$Fahrenheit     = $($Celsius -eq $Fahrenheit)
`$Celsius -ne `$Kelvin         = $($Celsius -ne $Kelvin)
"@
Temperatures are: 0.00°C, 32.00°F, 0.00°K

$Celsius.Equals($Fahrenheit) = True
$Celsius -eq $Fahrenheit     = True
$Celsius -ne $Kelvin         = True

Implementieren von "IComparable"

Die letzte Schnittstelle, die für die Temperaturklasse implementiert werden soll, ist System.IComparable. Wenn die Klasse diese Schnittstelle implementiert, können Benutzer die Instanzen der Klasse mithilfe von -lt, -le, -gtund -ge Operatoren vergleichen.

Um die Schnittstelle zu implementieren, muss die Klasse von System.IComparable erben und die Equals() Instanzmethode definieren. Die Equals() Methode muss über die folgende Signatur verfügen:

[int] CompareTo([Object]$Other) {
    # Implementation
}

Die Signatur, die die Schnittstelle benötigt, wird in der Referenzdokumentation aufgeführt.

Bei Temperatur sollte die Klasse nur den Vergleich von zwei Instanzen der Klasse unterstützen. Da der zugrunde liegende Typ für die Degrees-Eigenschaft selbst bei der Konvertierung in eine andere Skalierung eine Gleitkommazahl ist, kann die Methode den zugrunde liegenden Typ für den tatsächlichen Vergleich verwenden.

[int] CompareTo([object]$Other) {
    # If the other object's null, consider this instance "greater than" it
    if ($null -eq $Other) {
        return 1
    }
    # If the other object isn't a temperature, we can't compare it.
    $OtherTemperature = $Other -as [Temperature]
    if ($null -eq $OtherTemperature) {
        throw [System.ArgumentException]::new(
            "Object must be of type 'Temperature'."
        )
    }
    # Compare the temperatures as Kelvin.
    return $this.ToKelvin().CompareTo($OtherTemperature.ToKelvin())
}

Die endgültige Definition für die Temperaturklasse lautet:

class Temperature : System.IFormattable,
                    System.IComparable,
                    System.IEquatable[object] {
    # Instance properties
    [float]            $Degrees
    [TemperatureScale] $Scale

    # Constructors
    Temperature() {}
    Temperature([float] $Degrees)          { $this.Degrees = $Degrees }
    Temperature([TemperatureScale] $Scale) { $this.Scale = $Scale }
    Temperature([float] $Degrees, [TemperatureScale] $Scale) {
        $this.Degrees = $Degrees
        $this.Scale = $Scale
    }

    [float] ToKelvin() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees + 273.15 }
            Fahrenheit { return ($this.Degrees + 459.67) * 5 / 9 }
        }
        return $this.Degrees
    }
    [float] ToCelsius() {
        switch ($this.Scale) {
            Fahrenheit { return ($this.Degrees - 32) * 5 / 9 }
            Kelvin { return $this.Degrees - 273.15 }
        }
        return $this.Degrees
    }
    [float] ToFahrenheit() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees * 9 / 5 + 32 }
            Kelvin { return $this.Degrees * 9 / 5 - 459.67 }
        }
        return $this.Degrees
    }

    [string] ToString(
        [string]$Format,
        [System.IFormatProvider]$FormatProvider
    ) {
        # If format isn't specified, use the defined scale.
        if ([string]::IsNullOrEmpty($Format)) {
            $Format = switch ($this.Scale) {
                Celsius    { 'C' }
                Fahrenheit { 'F' }
                Kelvin     { 'K' }
            }
        }
        # If format provider isn't specified, use the current culture.
        if ($null -eq $FormatProvider) {
            $FormatProvider = [CultureInfo]::CurrentCulture
        }
        # Format the temperature.
        switch ($Format) {
            'C' {
                return $this.ToCelsius().ToString('F2', $FormatProvider) + '°C'
            }
            'F' {
                return $this.ToFahrenheit().ToString('F2', $FormatProvider) + '°F'
            }
            'K' {
                return $this.ToKelvin().ToString('F2', $FormatProvider) + '°K'
            }
        }
        # If we get here, the format is invalid.
        throw [System.FormatException]::new(
            "Unknown format: '$Format'. Valid Formats are 'C', 'F', and 'K'"
        )
    }

    [string] ToString([string]$Format) {
        return $this.ToString($Format, $null)
    }

    [string] ToString() {
        return $this.ToString($null, $null)
    }

    [bool] Equals([object]$Other) {
        # If the other object is null, we can't compare it.
        if ($null -eq $Other) {
            return $false
        }
        # If the other object isn't a temperature, we can't compare it.
        $OtherTemperature = $Other -as [Temperature]
        if ($null -eq $OtherTemperature) {
            return $false
        }
        # Compare the temperatures as Kelvin.
        return $this.ToKelvin() -eq $OtherTemperature.ToKelvin()
    }
    [int] CompareTo([object]$Other) {
        # If the other object's null, consider this instance "greater than" it
        if ($null -eq $Other) {
            return 1
        }
        # If the other object isn't a temperature, we can't compare it.
        $OtherTemperature = $Other -as [Temperature]
        if ($null -eq $OtherTemperature) {
            throw [System.ArgumentException]::new(
                "Object must be of type 'Temperature'."
            )
        }
        # Compare the temperatures as Kelvin.
        return $this.ToKelvin().CompareTo($OtherTemperature.ToKelvin())
    }
}

enum TemperatureScale {
    Celsius    = 0
    Fahrenheit = 1
    Kelvin     = 2
}

Mit der vollständigen Definition können Benutzer Instanzen der Klasse in PowerShell wie jeden integrierten Typ formatieren und vergleichen.

$Celsius    = [Temperature]::new()
$Fahrenheit = [Temperature]::new(32, 'Fahrenheit')
$Kelvin     = [Temperature]::new([TemperatureScale]::Kelvin)

@"
Temperatures are: $Celsius, $Fahrenheit, $Kelvin
`$Celsius.Equals(`$Fahrenheit)    = $($Celsius.Equals($Fahrenheit))
`$Celsius.Equals(`$Kelvin)        = $($Celsius.Equals($Kelvin))
`$Celsius.CompareTo(`$Fahrenheit) = $($Celsius.CompareTo($Fahrenheit))
`$Celsius.CompareTo(`$Kelvin)     = $($Celsius.CompareTo($Kelvin))
`$Celsius -lt `$Fahrenheit        = $($Celsius -lt $Fahrenheit)
`$Celsius -le `$Fahrenheit        = $($Celsius -le $Fahrenheit)
`$Celsius -eq `$Fahrenheit        = $($Celsius -eq $Fahrenheit)
`$Celsius -gt `$Kelvin            = $($Celsius -gt $Kelvin)
"@
Temperatures are: 0.00°C, 32.00°F, 0.00°K
$Celsius.Equals($Fahrenheit)    = True
$Celsius.Equals($Kelvin)        = False
$Celsius.CompareTo($Fahrenheit) = 0
$Celsius.CompareTo($Kelvin)     = 1
$Celsius -lt $Fahrenheit        = False
$Celsius -le $Fahrenheit        = True
$Celsius -eq $Fahrenheit        = True
$Celsius -gt $Kelvin            = True

Beispiel 3 : Erben von einer generischen Basisklasse

In diesem Beispiel wird gezeigt, wie Sie von einer generischen Klasse wie System.Collections.Generic.List abgeleitet werden können.

Verwenden einer integrierten Klasse als Typparameter

Führen Sie den folgenden Codeblock aus. Es zeigt, wie eine neue Klasse von einem generischen Typ erben kann, solange der Typparameter bereits zur Analysezeit definiert ist.

class ExampleStringList : System.Collections.Generic.List[string] {}

$List = [ExampleStringList]::New()
$List.AddRange([string[]]@('a','b','c'))
$List.GetType() | Format-List -Property Name, BaseType
$List
Name     : ExampleStringList
BaseType : System.Collections.Generic.List`1[System.String]

a
b
c

Verwenden einer benutzerdefinierten Klasse als Typparameter

Der nächste Codeblock definiert zuerst eine neue Klasse( ExampleItem) mit einer einzelnen Instanzeigenschaft und der ToString() Methode. Anschließend definiert sie die ExampleItemList-Klasse , die von der System.Collections.Generic.List-Basisklasse mit ExampleItem als Typparameter erbt.

Kopieren Sie den gesamten Codeblock, und führen Sie ihn als einzelne Anweisung aus.

class ExampleItem {
    [string] $Name
    [string] ToString() { return $this.Name }
}
class ExampleItemList : System.Collections.Generic.List[ExampleItem] {}
ParentContainsErrorRecordException: An error occurred while creating the pipeline.

Das Ausführen des gesamten Codeblocks löst einen Fehler aus, da PowerShell die ExampleItem-Klasse noch nicht in die Laufzeit geladen hat. Sie können den Klassennamen noch nicht als Typparameter für die Basisklasse System.Collections.Generic.List verwenden.

Führen Sie die folgenden Codeblöcke in der Reihenfolge aus, in der sie definiert sind.

class ExampleItem {
    [string] $Name
    [string] ToString() { return $this.Name }
}
class ExampleItemList : System.Collections.Generic.List[ExampleItem] {}

Dieses Mal löst PowerShell keine Fehler aus. Beide Klassen sind jetzt definiert. Führen Sie den folgenden Codeblock aus, um das Verhalten der neuen Klasse anzuzeigen.

$List = [ExampleItemList]::New()
$List.AddRange([ExampleItem[]]@(
    [ExampleItem]@{ Name = 'Foo' }
    [ExampleItem]@{ Name = 'Bar' }
    [ExampleItem]@{ Name = 'Baz' }
))
$List.GetType() | Format-List -Property Name, BaseType
$List
Name     : ExampleItemList
BaseType : System.Collections.Generic.List`1[ExampleItem]

Name
----
Foo
Bar
Baz

Ableiten eines generischen Typs mit einem benutzerdefinierten Typparameter in einem Modul

Die folgenden Codeblöcke zeigen, wie Sie eine Klasse definieren können, die von einer generischen Basisklasse erbt, die einen benutzerdefinierten Typ für den Typparameter verwendet.

Speichern Sie den folgenden Codeblock als GenericExample.psd1.

@{
    RootModule        = 'GenericExample.psm1'
    ModuleVersion     = '0.1.0'
    GUID              = '2779fa60-0b3b-4236-b592-9060c0661ac2'
}

Speichern Sie den folgenden Codeblock als GenericExample.InventoryItem.psm1.

class InventoryItem {
    [string] $Name
    [int]    $Count

    InventoryItem() {}
    InventoryItem([string]$Name) {
        $this.Name = $Name
    }
    InventoryItem([string]$Name, [int]$Count) {
        $this.Name  = $Name
        $this.Count = $Count
    }

    [string] ToString() {
        return "$($this.Name) ($($this.Count))"
    }
}

Speichern Sie den folgenden Codeblock als GenericExample.psm1.

using namespace System.Collections.Generic
using module ./GenericExample.InventoryItem.psm1

class Inventory : List[InventoryItem] {}

# Define the types to export with type accelerators.
$ExportableTypes =@(
    [InventoryItem]
    [Inventory]
)
# Get the internal TypeAccelerators class to use its static methods.
$TypeAcceleratorsClass = [psobject].Assembly.GetType(
    'System.Management.Automation.TypeAccelerators'
)
# Ensure none of the types would clobber an existing type accelerator.
# If a type accelerator with the same name exists, throw an exception.
$ExistingTypeAccelerators = $TypeAcceleratorsClass::Get
foreach ($Type in $ExportableTypes) {
    if ($Type.FullName -in $ExistingTypeAccelerators.Keys) {
        $Message = @(
            "Unable to register type accelerator '$($Type.FullName)'"
            'Accelerator already exists.'
        ) -join ' - '

        throw [System.Management.Automation.ErrorRecord]::new(
            [System.InvalidOperationException]::new($Message),
            'TypeAcceleratorAlreadyExists',
            [System.Management.Automation.ErrorCategory]::InvalidOperation,
            $Type.FullName
        )
    }
}
# Add type accelerators for every exportable type.
foreach ($Type in $ExportableTypes) {
    $TypeAcceleratorsClass::Add($Type.FullName, $Type)
}
# Remove type accelerators when the module is removed.
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
    foreach($Type in $ExportableTypes) {
        $TypeAcceleratorsClass::Remove($Type.FullName)
    }
}.GetNewClosure()

Tipp

Das Stammmodul fügt die benutzerdefinierten Typen den Zugriffstasten von PowerShell hinzu. Mit diesem Muster können Modulbenutzer sofort auf IntelliSense und AutoVervollständigen für die benutzerdefinierten Typen zugreifen, ohne zuerst die using module Anweisung verwenden zu müssen.

Weitere Informationen zu diesem Muster finden Sie im Abschnitt "Exportieren mit Typbeschleunigern" von about_Classes.

Importieren Sie das Modul, und überprüfen Sie die Ausgabe.

Import-Module ./GenericExample.psd1

$Inventory = [Inventory]::new()
$Inventory.GetType() | Format-List -Property Name, BaseType

$Inventory.Add([InventoryItem]::new('Bucket', 2))
$Inventory.Add([InventoryItem]::new('Mop'))
$Inventory.Add([InventoryItem]@{ Name = 'Broom' ; Count = 4 })
$Inventory
Name     : Inventory
BaseType : System.Collections.Generic.List`1[InventoryItem]

Name   Count
----   -----
Bucket     2
Mop        0
Broom      4

Das Modul wird ohne Fehler geladen, da die InventoryItem-Klasse in einer anderen Moduldatei als die Inventory-Klasse definiert ist. Beide Klassen stehen Modulbenutzern zur Verfügung.

Erben einer Basisklasse

Wenn eine Klasse von einer Basisklasse erbt, erbt sie die Eigenschaften und Methoden der Basisklasse. Sie erbt die Basisklassenkonstruktoren nicht direkt, kann sie jedoch aufrufen.

Wenn die Basisklasse in .NET und nicht in PowerShell definiert ist, beachten Sie Folgendes:

  • PowerShell-Klassen können nicht von versiegelten Klassen erben.
  • Beim Erben von einer generischen Basisklasse kann der Typparameter für die generische Klasse nicht die abgeleitete Klasse sein. Die Verwendung der abgeleiteten Klasse als Typparameter löst einen Analysefehler aus.

Informationen zur Funktionsweise von Vererbung und Außerkraftsetzung für abgeleitete Klassen finden Sie in Beispiel 1.

Abgeleitete Klassenkonstruktoren

Abgeleitete Klassen erben nicht direkt die Konstruktoren der Basisklasse. Wenn die Basisklasse einen Standardkonstruktor definiert und die abgeleitete Klasse keine Konstruktoren definiert, verwenden neue Instanzen der abgeleiteten Klasse den Standardkonstruktor der Basisklasse. Wenn die Basisklasse keinen Standardkonstruktor definiert, muss abgeleitete Klasse explizit mindestens einen Konstruktor definieren.

Abgeleitete Klassenkonstruktoren können einen Konstruktor aus der Basisklasse mit dem base Schlüsselwort (keyword) aufrufen. Wenn die abgeleitete Klasse keinen Konstruktor aus der Basisklasse explizit aufruft, ruft sie stattdessen den Standardkonstruktor für die Basisklasse auf.

Um einen nicht standardmäßigen Basiskonstruktor aufzurufen, fügen Sie : base(<parameters>) nach den Konstruktorparametern und vor dem Textkörperblock hinzu.

class <derived-class> : <base-class> {
    <derived-class>(<derived-parameters>) : <base-class>(<base-parameters>) {
        # initialization code
    }
}

Beim Definieren eines Konstruktors, der einen Basisklassenkonstruktor aufruft, können die Parameter eines der folgenden Elemente sein:

  • Die Variable eines parameters für den abgeleiteten Klassenkonstruktor.
  • Ein beliebiger statischer Wert.
  • Jeder Ausdruck, der zu einem Wert des Parametertyps ausgewertet wird.

Die Illustration-Klasse in Beispiel 1 zeigt, wie eine abgeleitete Klasse die Basisklassenkonstruktoren verwenden kann.

Abgeleitete Klassenmethoden

Wenn eine Klasse von einer Basisklasse abgeleitet wird, erbt sie die Methoden der Basisklasse und deren Überladungen. Alle für die Basisklasse definierten Methodenüberladungen, einschließlich ausgeblendeter Methoden, sind für die abgeleitete Klasse verfügbar.

Eine abgeleitete Klasse kann eine geerbte Methodenüberladung überschreiben, indem sie in der Klassendefinition neu definiert wird. Um die Überladung außer Kraft zu setzen, müssen die Parametertypen mit der Basisklasse identisch sein. Der Ausgabetyp für die Überladung kann unterschiedlich sein.

Im Gegensatz zu Konstruktoren können Methoden die : base(<parameters>) Syntax nicht verwenden, um eine Basisklassenüberladung für die Methode aufzurufen. Die neu definierte Überladung für die abgeleitete Klasse ersetzt die durch die Basisklasse definierte Überladung vollständig. Um die Basisklassenmethode für eine Instanz aufzurufen, wandeln Sie die Instanzvariable ($this) in die Basisklasse um, bevor Sie die Methode aufrufen.

Der folgende Codeausschnitt zeigt, wie eine abgeleitete Klasse die Basisklassenmethode aufrufen kann.

class BaseClass {
    [bool] IsTrue() { return $true }
}
class DerivedClass : BaseClass {
    [bool] IsTrue()     { return $false }
    [bool] BaseIsTrue() { return ([BaseClass]$this).IsTrue() }
}

@"
[BaseClass]::new().IsTrue()        = $([BaseClass]::new().IsTrue())
[DerivedClass]::new().IsTrue()     = $([DerivedClass]::new().IsTrue())
[DerivedClass]::new().BaseIsTrue() = $([DerivedClass]::new().BaseIsTrue())
"@
[BaseClass]::new().IsTrue()        = True
[DerivedClass]::new().IsTrue()     = False
[DerivedClass]::new().BaseIsTrue() = True

Ein erweitertes Beispiel, das zeigt, wie eine abgeleitete Klasse geerbte Methoden außer Kraft setzen kann, finden Sie in Beispiel 1 in der Illustrationsklasse.

Abgeleitete Klasseneigenschaften

Wenn eine Klasse von einer Basisklasse abgeleitet wird, erbt sie die Eigenschaften der Basisklasse. Alle für die Basisklasse definierten Eigenschaften, einschließlich ausgeblendeter Eigenschaften, sind für die abgeleitete Klasse verfügbar.

Eine abgeleitete Klasse kann eine geerbte Eigenschaft überschreiben, indem sie in der Klassendefinition neu definiert wird. Die Eigenschaft für die abgeleitete Klasse verwendet ggf. den neu definierten Typ und standardwert. Wenn die geerbte Eigenschaft einen Standardwert definiert hat und die neu definierte Eigenschaft nicht, hat die geerbte Eigenschaft keinen Standardwert.

Wenn eine abgeleitete Klasse eine statische Eigenschaft nicht überschreibt, greift der Zugriff auf die statische Eigenschaft über die abgeleitete Klasse auf die statische Eigenschaft der Basisklasse zu. Durch Ändern des Eigenschaftswerts durch die abgeleitete Klasse wird der Wert für die Basisklasse geändert. Jede andere abgeleitete Klasse, die die statische Eigenschaft nicht überschreibt, verwendet auch den Wert der Eigenschaft für die Basisklasse. Das Aktualisieren des Werts einer geerbten statischen Eigenschaft in einer Klasse, die die Eigenschaft nicht überschreibt, hat möglicherweise unbeabsichtigte Effekte für Klassen, die von derselben Basisklasse abgeleitet wurden.

In Beispiel 1 wird gezeigt, wie abgeleitete Klassen erben, erweitern und überschreiben.

Ableiten von Generika

Wenn eine Klasse von einem generischen abgeleitet wird, muss der Typparameter bereits definiert werden, bevor PowerShell die abgeleitete Klasse analysiert. Wenn der Typparameter für das Generische eine PowerShell-Klasse oder -Aufzählung ist, die in derselben Datei oder einem Codeblock definiert ist, löst PowerShell einen Fehler aus.

Um eine Klasse von einer generischen Basisklasse mit einem benutzerdefinierten Typ als Typparameter abzuleiten, definieren Sie die Klasse oder Enumeration für den Typparameter in einer anderen Datei oder einem anderen Modul, und verwenden Sie die using module Anweisung, um die Typdefinition zu laden.

Ein Beispiel, das zeigt, wie sie von einer generischen Basisklasse erben, finden Sie unter Beispiel 3.

Nützliche Klassen, die geerbt werden sollen

Es gibt einige Klassen, die beim Erstellen von PowerShell-Modulen hilfreich sein können. In diesem Abschnitt werden einige Basisklassen aufgeführt, für die eine von ihnen abgeleitete Klasse verwendet werden kann.

  • System.Attribute – Leiten Sie Klassen ab, um Attribute zu definieren, die für Variablen, Parameter, Klassen- und Enumerationsdefinitionen und vieles mehr verwendet werden können.
  • System.Management.Automation.ArgumentTransformationAttribute – Leiten Sie Klassen ab, um die Konvertierung von Eingaben für eine Variable oder einen Parameter in einen bestimmten Datentyp zu verarbeiten.
  • System.Management.Automation.ValidateArgumentsAttribute – Leiten Sie Klassen ab, um eine benutzerdefinierte Überprüfung auf Variablen, Parameter und Klasseneigenschaften anzuwenden.
  • System.Collections.Generic.List – Leiten Sie Klassen ab, um das Erstellen und Verwalten von Listen eines bestimmten Datentyps zu vereinfachen.
  • System.Exception – Leiten Sie Klassen ab, um benutzerdefinierte Fehler zu definieren.

Implementieren von Schnittstellen

Eine PowerShell-Klasse, die eine Schnittstelle implementiert, muss alle Member dieser Schnittstelle implementieren. Das Weglassen der Implementierungsschnittstellenmember führt zu einem Analysezeitfehler im Skript.

Hinweis

PowerShell unterstützt das Deklarieren neuer Schnittstellen im PowerShell-Skript nicht. Stattdessen müssen Schnittstellen im .NET-Code deklariert und der Sitzung mit dem Add-Type Cmdlet oder der using assembly Anweisung hinzugefügt werden.

Wenn eine Klasse eine Schnittstelle implementiert, kann sie wie jede andere Klasse verwendet werden, die diese Schnittstelle implementiert. Einige Befehle und Vorgänge beschränken ihre unterstützten Typen auf Klassen, die eine bestimmte Schnittstelle implementieren.

Eine Beispielimplementierung von Schnittstellen finden Sie unter Beispiel 2.

Nützliche Schnittstellen zur Implementierung

Es gibt einige Schnittstellenklassen, die beim Erstellen von PowerShell-Modulen hilfreich sein können. In diesem Abschnitt werden einige Basisklassen aufgeführt, für die eine von ihnen abgeleitete Klasse verwendet werden kann.

  • System.IEquatable – Mit dieser Schnittstelle können Benutzer zwei Instanzen der Klasse vergleichen. Wenn eine Klasse diese Schnittstelle nicht implementiert, sucht PowerShell anhand der Referenzgleichheit nach Äquivalenz zwischen zwei Instanzen. Mit anderen Worten, eine Instanz der Klasse ist nur gleich sich selbst, auch wenn die Eigenschaftswerte auf zwei Instanzen gleich sind.
  • System.IComparable – Mit dieser Schnittstelle können Benutzer Instanzen der Klasse mit den -leOperatoren , -lt, -geund -gt Vergleichsoperatoren vergleichen. Wenn eine Klasse diese Schnittstelle nicht implementiert, lösen diese Operatoren einen Fehler aus.
  • System.IFormattable – Mit dieser Schnittstelle können Benutzer Instanzen der Klasse in verschiedene Zeichenfolgen formatieren. Dies ist nützlich für Klassen, die mehr als eine Standardzeichenfolgendarstellung aufweisen, z. B. Budgetelemente, Literaturverzeichnisse und Temperaturen.
  • System.IConvertible – Mit dieser Schnittstelle können Benutzer Instanzen der Klasse in andere Laufzeittypen konvertieren. Dies ist nützlich für Klassen mit einem zugrunde liegenden numerischen Wert oder kann in einen konvertiert werden.

Begrenzungen

  • PowerShell unterstützt das Definieren von Schnittstellen im Skriptcode nicht.

    Problemumgehung: Definieren von Schnittstellen in C# und Verweisen auf die Assembly, die die Schnittstellen definiert.

  • PowerShell-Klassen können nur von einer Basisklasse erben.

    Problemumgehung: Die Klassenvererbung ist transitiv. Eine abgeleitete Klasse kann von einer anderen abgeleiteten Klasse erben, um die Eigenschaften und Methoden einer Basisklasse abzurufen.

  • Beim Erben von einer generischen Klasse oder Schnittstelle muss der Typparameter für das generische Element bereits definiert sein. Eine Klasse kann sich nicht als Typparameter für eine Klasse oder Schnittstelle definieren.

    Problemumgehung: Um von einer generischen Basisklasse oder Schnittstelle abzuleiten, definieren Sie den benutzerdefinierten Typ in einer anderen .psm1 Datei, und verwenden Sie die using module Anweisung, um den Typ zu laden. Es gibt keine Problemumgehung für einen benutzerdefinierten Typ, um sich selbst als Typparameter zu verwenden, wenn er von einer generischen Erbung erbt.

Weitere Informationen