Freigeben über


about_Classes_Inheritance

Kurzbeschreibung

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

Lange Beschreibung

PowerShell-Klassen unterstützen Vererbung, wodurch Sie eine untergeordnete Klasse definieren können, die das Verhalten einer übergeordneten Klasse wiederverwendet (erbt), erweitert oder ändert. Die Klasse, deren Mitglieder geerbt werden, wird als die sogenannte Basisklassebezeichnet. Die Klasse, die die Mitglieder der Basisklasse erbt, wird als abgeleitete Klassebezeichnet.

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

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

  • Statische Konstruktoren, die die statischen Daten einer Klasse initialisieren.
  • Instanzkonstruktoren, die Sie aufrufen, um eine neue Instanz der Klasse zu erstellen. Jede Klasse muss eigene 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 Basisklassenmitglieder 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 -eq-Operator sucht beispielsweise nur nach Bezugsgleichheit, es sei denn, die Klasse implementiert die System.IEquatable-Schnittstelle. Die Operatoren -le, -lt, -geund -gt 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
}

Examples

Beispiel 1 : Erben und Überschreiben von einer Basisklasse

Das folgende Beispiel zeigt das Verhalten geerbter Eigenschaften mit und ohne Überschreibung. 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 hat zwei statische Eigenschaften, List und Artists. Als Nächstes definiert er die statische RegisterWork()-Methode, um Werke zur statischen -List--Eigenschaft und Künstler zur -Artists--Eigenschaft hinzuzufügen, wobei bei jedem neuen Eintrag in den Listen eine Nachricht geschrieben wird.

Die Klasse definiert drei Instanzeigenschaften, die ein veröffentlichtes Werk beschreiben. Schließlich werden die Register()- und ToString() Instanzmethoden definiert.

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. Es fügt eine neue Instanz-Eigenschaft Genreshinzu, die nicht in der Basisklasse vorhanden ist.

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

Der folgende Codeblock zeigt das Verhalten der abgeleiteten Album Klasse. Zunächst wird die $VerbosePreference so eingestellt, 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, obwohl die Album Klasse keinen Wert für Category oder irgendwelche Konstruktoren definiert hat, die Eigenschaft vom Standardkonstruktor der Basisklasse definiert wurde.

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

Der nächste Codeblock löscht die Registrierung und ruft die Register() Instanzmethode für die objekte Album 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 das Album-Objekte hat die gleiche 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 Überschreibungen

Der nächste Codeblock definiert die Illustration Klasse, die von der Basisklasse PublishedWork Klasse erbt. Die neue Klasse erweitert die Basisklasse, indem die Medium-Instanzeigenschaft mit dem Standardwert Unknown definiert wird.

Im Gegensatz zur abgeleiteten Album-Klasse überschreibt Illustration die folgenden Eigenschaften und Methoden:

  • Es überschreibt die statische Eigenschaft für Artists. Die Definition ist identisch, aber die Illustration Klasse deklariert sie direkt.
  • Er überschreibt die Category-Instanzeigenschaft und legt den Standardwert auf Illustrations fest.
  • 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 Methode der Basisklasse RegisterWork() aufzurufen, und fügt den Künstler dann der überschriebenen Artists statischen Eigenschaft der abgeleiteten 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 Illustration Klasse. 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

Die ausführliche Meldung 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 Meldung 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 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 der 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 Album-Instanzen geben nur den Namen und den Künstler in ihrem Text zurück. Die Illustration-Instanzen enthielten auch das Medium in Klammern, da diese Klasse die ToString()-Methode überschrieben hatte.

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, Degrees 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 Temperature:

  • Als Strings nicht korrekt angezeigt werden.
  • Die Ä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 Temperature Klasse 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 Referenzdokumentationaufgeführt.

Für Temperatur-sollte die Klasse drei Formate unterstützen: C die Instanz in Celsius zurückzugeben, F sie in Fahrenheit zurückzugeben, und K es 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 das Format und die aktuelle Kultur beim Formatieren des numerischen Gradwerts verwendet. Sie verwendet die To<Scale>()-Instanzmethoden, um die Gradwerte zu konvertieren, sie auf zwei Dezimalstellen zu formatieren und das entsprechende Gradsymbol an die Zeichenfolge anzuhängen.

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 Temperature:

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 nun die Temperature Klasse zur Lesbarkeit formatiert werden kann, müssen Die Benutzer in der Lage sein, zu überprüfen, ob zwei Instanzen der Klasse gleich sind. Um diesen Test zu unterstützen, 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 die folgende Signatur aufweisen:

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

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

Für Temperaturesollte die Klasse nur den Vergleich von zwei Instanzen der Klasse unterstützen. Für einen anderen Wert oder Typ, einschließlich $null, sollte er $falsezurückgeben. 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 Temperature:

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 Temperature Klasse implementiert werden soll, ist System.IComparable. Wenn die Klasse diese Schnittstelle implementiert, können Benutzer die Operatoren -lt, -le, -gtund -ge verwenden, um Instanzen der Klasse zu vergleichen.

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

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

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

Für Temperaturesollte die Klasse nur den Vergleich von zwei Instanzen der Klasse unterstützen. Da der zugrunde liegende Typ für die eigenschaft "Degrees" selbst bei der Konvertierung in eine andere Skalierung eine Gleitkommazahl ist, kann die Methode auf den zugrunde liegenden Typ für den tatsächlichen Vergleich zurückgreifen.

[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 klasse Temperature 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 einem generischen Typ abgeleitet werden können, solange der Typparameter zur Analysezeit bereits definiert ist.

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 wird die ExampleItemList Klasse definiert, 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 System.Collections.Generic.List Basisklasse 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 Rootmodul fügt die benutzerdefinierten Typen den Typbeschleunigern 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 Überschreiben für abgeleitete Klassen finden Sie unter 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 schlüsselwort base 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 des abgeleiteten Klassenkonstruktors.
  • 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 Typ der Ausgabe bei Ü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 der Illustration Klasse in Beispiel 1.

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.

Beispiel 1 zeigt, wie abgeleitete Klassen die Eigenschaften der Basisklasse erben, erweitern und überschreiben.

Ableiten von generischen

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 zum Erben einer generischen Basisklasse finden Sie unter Beispiel 3.

Nützliche Klassen zum Vererben

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 durchzuführen.
  • System.Management.Automation.ValidateArgumentsAttribute — Leiten Sie Klassen ab, um benutzerdefinierte Überprüfungen 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 muss, wenn sie eine Schnittstelle implementiert, alle Mitglieder dieser Schnittstelle implementieren. Das Weglassen der Implementierungsschnittstellenmitglieder verursacht einen Analysefehler im Skript.

Anmerkung

PowerShell unterstützt das Deklarieren neuer Schnittstellen im PowerShell-Skript nicht. Stattdessen müssen Schnittstellen im .NET-Code deklariert und der Sitzung mit dem Cmdlet Add-Type 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 – Diese Schnittstelle ermöglicht es Benutzern, Instanzen der Klasse mit den Vergleichsoperatoren -le, -lt, -geund -gt zu 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.

Einschränkungen

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

Siehe auch