Udostępnij za pośrednictwem


about_Classes_Inheritance

Krótki opis

Opisuje sposób definiowania klas, które rozszerzają inne typy.

Długi opis

Klasy programu PowerShell obsługują dziedziczenie, które umożliwia zdefiniowanie klasy podrzędnej, która ponownie używa (dziedziczy), rozszerza lub modyfikuje zachowanie klasy nadrzędnej. Klasa, której składowe są dziedziczone, jest nazywana klasą bazową. Klasa, która dziedziczy składowe klasy bazowej, jest nazywana klasą pochodną.

Program PowerShell obsługuje tylko pojedyncze dziedziczenie. Klasa może dziedziczyć tylko z jednej klasy. Jednak dziedziczenie jest przechodnie, co umożliwia zdefiniowanie hierarchii dziedziczenia dla zestawu typów. Innymi słowy, typ D może dziedziczyć po typie C, który dziedziczy z typu B, który dziedziczy z typu A klasy bazowej. Ponieważ dziedziczenie jest przechodnie, elementy członkowskie typu A są dostępne dla typu D.

Klasy pochodne nie dziedziczą wszystkich składowych klasy bazowej. Następujące elementy członkowskie nie są dziedziczone:

  • Konstruktory statyczne, które inicjują dane statyczne klasy.
  • Konstruktory wystąpień, które są wywoływane w celu utworzenia nowego wystąpienia klasy. Każda klasa musi definiować własne konstruktory.

Klasę można rozszerzyć, tworząc nową klasę pochodzącą z istniejącej klasy. Klasa pochodna dziedziczy właściwości i metody klasy bazowej. Możesz dodać lub zastąpić składowe klasy bazowej zgodnie z wymaganiami.

Klasy mogą również dziedziczyć z interfejsów, które definiują kontrakt. Klasa dziedziczona z interfejsu musi implementować ten kontrakt. W takim przypadku klasa może być użyteczna jak każda inna klasa implementujący ten interfejs. Jeśli klasa dziedziczy z interfejsu, ale nie implementuje interfejsu, program PowerShell zgłasza błąd analizy dla klasy.

Niektóre operatory programu PowerShell zależą od klasy implementowania określonego interfejsu. Na przykład operator sprawdza równość odwołania tylko wtedy, -eq gdy klasa implementuje interfejs System.IEquatable . Operatory , , i -gt działają tylko na klasach implementujących interfejs System.IComparable. -ge-lt-le

Klasa pochodna : używa składni do rozszerzania klasy bazowej lub implementowania interfejsów. Klasa pochodna powinna zawsze znajdować się w lewej części deklaracji klasy.

W tym przykładzie przedstawiono podstawową składnię dziedziczenia klas programu PowerShell.

Class Derived : Base {...}

W tym przykładzie pokazano dziedziczenie z deklaracją interfejsu przychodzącą po klasie bazowej.

Class Derived : Base, Interface {...}

Składnia

Dziedziczenie klas używa następujących składni:

Składnia jednego wiersza

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

Na przykład:

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

Składnia wielowierszowa

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

Na przykład:

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

Przykłady

Przykład 1 — dziedziczenie i zastępowanie z klasy bazowej

W poniższym przykładzie pokazano zachowanie dziedziczych właściwości z przesłonięciami i bez ich zastępowania. Uruchom bloki kodu w kolejności po przeczytaniu ich opisu.

Definiowanie klasy bazowej

Pierwszy blok kodu definiuje PublishedWork jako klasę bazową. Ma dwie statyczne właściwości: List i Artists. Następnie definiuje statyczną RegisterWork() metodę dodawania prac do właściwości listy statycznej i artystów do właściwości Artists, pisząc komunikat dla każdego nowego wpisu na listach.

Klasa definiuje trzy właściwości wystąpienia, które opisują opublikowaną pracę. Na koniec definiuje Register() metody i ToString() wystąpienia.

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)" }
}

Definiowanie klasy pochodnej bez przesłonięć

Pierwsza klasa pochodna to Album. Nie zastępuje żadnych właściwości ani metod. Dodaje nową właściwość wystąpienia, Gatunek, która nie istnieje w klasie bazowej.

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

Poniższy blok kodu przedstawia zachowanie pochodnej klasy Album . Najpierw ustawia $VerbosePreference wartość tak, aby komunikaty z metod klasy emitować do konsoli programu . Tworzy trzy wystąpienia klasy, pokazuje je w tabeli, a następnie rejestruje je przy użyciu dziedziczonej metody statycznej RegisterWork() . Następnie wywołuje tę samą metodę statyczną bezpośrednio w klasie bazowej.

$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.

Zwróć uwagę, że mimo że klasa Album nie zdefiniowała wartości dla kategorii ani żadnych konstruktorów, właściwość została zdefiniowana przez domyślny konstruktor klasy bazowej.

W pełnej wiadomości drugie wywołanie RegisterWork() metody zgłasza, że prace i artyści są już zarejestrowane. Mimo że pierwsze wywołanie RegisterWork() klasy albumu pochodnego było używane dziedziczonej metody statycznej z podstawowej klasy PublishedWork. Ta metoda zaktualizowała właściwości listy statycznej i artysty w klasie bazowej, która nie została zastąpiona przez klasę pochodną.

Następny blok kodu czyści rejestr i wywołuje metodę Register() wystąpienia w obiektach Album .

[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

Metoda wystąpienia w obiektach Album ma taki sam wpływ, jak wywoływanie metody statycznej w klasie pochodnej lub bazowej.

Poniższy blok kodu porównuje właściwości statyczne dla klasy bazowej i klasy pochodnej, pokazując, że są one takie same.

[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

Definiowanie klasy pochodnej za pomocą przesłonięć

Następny blok kodu definiuje klasę Ilustracja dziedziczącą z podstawowej klasy PublishedWork . Nowa klasa rozszerza klasę bazową, definiując właściwość Medium wystąpienia z wartością Unknowndomyślną .

W przeciwieństwie do klasy pochodnego albumu ilustracja zastępuje następujące właściwości i metody:

  • Zastępuje on właściwość static Artists . Definicja jest taka sama, ale klasa Ilustracja deklaruje ją bezpośrednio.
  • Zastępuje właściwość Wystąpienia kategorii, ustawiając wartość domyślną na Illustrations.
  • Zastępuje metodę ToString() wystąpienia, tak aby ciąg reprezentujący ilustrację zawierał nośnik, za pomocą którego został utworzony.

Klasa definiuje również metodę statyczną RegisterIllustration() , aby najpierw wywołać metodę klasy RegisterWork() bazowej, a następnie dodać artystę do przesłoniętej właściwości statycznej Artists w klasie pochodnej.

Na koniec klasa zastępuje wszystkie trzy konstruktory:

  1. Domyślny konstruktor jest pusty, z wyjątkiem pełnej wiadomości wskazującej, że utworzył ilustrację.
  2. Następny konstruktor przyjmuje dwie wartości ciągu dla nazwy i artysty, które utworzyły ilustrację. Zamiast implementowania logiki ustawiania właściwości Name i Artist , konstruktor wywołuje odpowiedni konstruktor z klasy bazowej.
  3. Ostatni konstruktor przyjmuje trzy wartości ciągu dla nazwy, artysty i medium ilustracji. Oba konstruktory zapisują pełny komunikat wskazujący, że utworzyli ilustrację.
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"
    }
}

Poniższy blok kodu przedstawia zachowanie pochodnej klasy Ilustracja . Tworzy trzy wystąpienia klasy, pokazuje je w tabeli, a następnie rejestruje je przy użyciu dziedziczonej metody statycznej RegisterWork() . Następnie wywołuje tę samą metodę statyczną bezpośrednio w klasie bazowej. Na koniec zapisuje komunikaty przedstawiające listę zarejestrowanych artystów dla klasy bazowej i klasy pochodnej.

$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

Pełne komunikaty z tworzenia wystąpień pokazują, że:

  • Podczas tworzenia pierwszego wystąpienia konstruktor domyślny klasy bazowej został wywołany przed konstruktorem domyślnym klasy pochodnej.
  • Podczas tworzenia drugiego wystąpienia konstruktor jawnie dziedziczony był wywoływany dla klasy bazowej przed konstruktorem klasy pochodnej.
  • Podczas tworzenia trzeciego wystąpienia konstruktor domyślny klasy bazowej został wywołany przed konstruktorem klasy pochodnej.

Pełne komunikaty z RegisterWork() metody wskazują, że dzieła i artyści zostali już zarejestrowani. Jest to spowodowane tym, że metoda nazywana RegisterIllustration() RegisterWork() jest metodą wewnętrznie.

Jednak podczas porównywania wartości właściwości static Artist zarówno dla klasy bazowej, jak i klasy pochodnej wartości są różne. Właściwość Artists dla klasy pochodnej obejmuje tylko ilustratorów, a nie artystów albumów. Ponowne zdefiniowanie właściwości Artist w klasie pochodnej uniemożliwia klasie zwracanie właściwości statycznej w klasie bazowej.

Końcowy blok kodu wywołuje metodę ToString() we wpisach właściwości static List w klasie bazowej.

[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)

Wystąpienia albumu zwracają tylko nazwę i artystę w swoim ciągu. Wystąpienia ilustracji zawierały również medium w nawiasach, ponieważ ta klasa zastępuje metodę ToString() .

Przykład 2 — Implementowanie interfejsów

W poniższym przykładzie pokazano, jak klasa może zaimplementować jeden lub więcej interfejsów. Przykład rozszerza definicję klasy Temperature w celu obsługi większej liczby operacji i zachowań.

Początkowa definicja klasy

Przed zaimplementowaniem jakichkolwiek interfejsów klasa Temperature jest definiowana z dwiema właściwościami, stopniami i skalą. Definiuje konstruktory i trzy metody wystąpienia do zwracania wystąpienia jako stopni określonej skali.

Klasa definiuje dostępne skalowanie przy użyciu wyliczenia TemperatureScale .

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
}

Jednak w tej podstawowej implementacji istnieje kilka ograniczeń, jak pokazano w następujących przykładowych danych wyjściowych:

$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.

Dane wyjściowe pokazują, że wystąpienia temperatury:

  • Nie wyświetlaj poprawnie jako ciągów.
  • Nie można sprawdzić prawidłowo pod kątem równoważności.
  • Nie można porównać.

Te trzy problemy można rozwiązać, implementując interfejsy dla klasy.

Implementowanie tabeli IFormattable

Pierwszym interfejsem do zaimplementowania dla klasy Temperature jest System.IFormattable. Ten interfejs umożliwia formatowanie wystąpienia klasy jako różnych ciągów. Aby zaimplementować interfejs, klasa musi dziedziczyć z tabeli System.IFormattable i definiować metodę ToString() wystąpienia.

Metoda ToString() wystąpienia musi mieć następujący podpis:

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

Podpis wymagany przez interfejs znajduje się w dokumentacji referencyjnej.

W przypadku klasy Temperature klasa powinna obsługiwać trzy formaty: C aby zwrócić wystąpienie w stopniach Celsjusza, F aby zwrócić je w fahrenheita i K zwrócić je w Kelvin. W przypadku dowolnego innego formatu metoda powinna zgłosić wyjątek System.FormatException.

[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'"
    )
}

W tej implementacji metoda jest domyślnie skalowana dla wystąpienia dla formatu i bieżącej kultury podczas formatowania samej wartości stopnia liczbowego. Używa To<Scale>() metod wystąpienia do konwertowania stopni, formatuje je do dwóch miejsc dziesiętnych i dołącza odpowiedni symbol stopnia do ciągu.

Dzięki zaimplementowaniu wymaganego podpisu klasa może również definiować przeciążenia, aby ułatwić zwracanie sformatowanego wystąpienia.

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

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

Poniższy kod przedstawia zaktualizowaną definicję temperatury:

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
}

Dane wyjściowe przeciążeń metody są wyświetlane w poniższym bloku.

$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

Implementowanie elementu IEquatable

Teraz, gdy klasę Temperature można sformatować pod kątem czytelności, użytkownicy muszą mieć możliwość sprawdzenia, czy dwa wystąpienia klasy są równe. Aby obsłużyć ten test, klasa musi zaimplementować interfejs System.IEquatable .

Aby zaimplementować interfejs, klasa musi dziedziczyć z klasy System.IEquatable i definiować metodę Equals() wystąpienia. Metoda Equals() musi mieć następujący podpis:

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

Podpis wymagany przez interfejs znajduje się w dokumentacji referencyjnej.

W przypadku klasy Temperature klasa powinna obsługiwać tylko porównanie dwóch wystąpień klasy. W przypadku dowolnej innej wartości lub typu, w tym $null, powinna zwrócić wartość $false. Podczas porównywania dwóch temperatur metoda powinna przekonwertować obie wartości na Kelvin, ponieważ temperatury mogą być równoważne nawet w przypadku różnych skali.

[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()
}

Po zaimplementowaniu metody interfejsu zaktualizowana definicja temperatury to:

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
}

Poniższy blok pokazuje, jak zachowuje się zaktualizowana klasa:

$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

Implementowanie IComparable

Ostatnim interfejsem do zaimplementowania dla klasy Temperature jest System.IComparable. Gdy klasa implementuje ten interfejs, użytkownicy mogą używać -ltoperatorów , -le, -gti -ge do porównywania wystąpień klasy .

Aby zaimplementować interfejs, klasa musi dziedziczyć z klasy System.IComparable i definiować metodę Equals() wystąpienia. Metoda Equals() musi mieć następujący podpis:

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

Podpis wymagany przez interfejs znajduje się w dokumentacji referencyjnej.

W przypadku klasy Temperature klasa powinna obsługiwać tylko porównanie dwóch wystąpień klasy. Ponieważ podstawowy typ właściwości Degrees , nawet w przypadku konwersji na inną skalę, jest liczbą zmiennoprzecinkową, metoda może polegać na podstawowym typie rzeczywistego porównania.

[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())
}

Ostateczna definicja klasy Temperature to:

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
}

Dzięki pełnej definicji użytkownicy mogą formatować i porównywać wystąpienia klasy w programie PowerShell, podobnie jak w przypadku każdego typu wbudowanego.

$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

Przykład 3 — dziedziczenie z ogólnej klasy bazowej

W tym przykładzie pokazano, jak można pochodzić z klasy ogólnej, takiej jak System.Collections.Generic.List.

Używanie wbudowanej klasy jako parametru typu

Uruchom następujący blok kodu. Pokazuje, jak nowa klasa może dziedziczyć z typu ogólnego, o ile parametr typu jest już zdefiniowany w czasie analizy.

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

Używanie klasy niestandardowej jako parametru typu

Następny blok kodu najpierw definiuje nową klasę ExampleItem z pojedynczą właściwością wystąpienia i ToString() metodą . Następnie definiuje klasę ExampleItemList dziedziczącą z klasy bazowej System.Collections.Generic.List z parametrem ExampleItem .

Skopiuj cały blok kodu i uruchom go jako pojedynczą instrukcję.

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

Uruchomienie całego bloku kodu powoduje wystąpienie błędu, ponieważ program PowerShell nie załadował jeszcze klasy ExampleItem do środowiska uruchomieniowego. Nie można jeszcze użyć nazwy klasy jako parametru typu klasy System.Collections.Generic.List .

Uruchom następujące bloki kodu w kolejności, w której są zdefiniowane.

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

Tym razem program PowerShell nie zgłasza żadnych błędów. Obie klasy są teraz zdefiniowane. Uruchom następujący blok kodu, aby wyświetlić zachowanie nowej klasy.

$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

Wyprowadzanie ogólnego z parametrem typu niestandardowego w module

Poniższe bloki kodu pokazują, jak można zdefiniować klasę, która dziedziczy z ogólnej klasy bazowej, która używa niestandardowego typu dla parametru typu.

Zapisz następujący blok kodu jako GenericExample.psd1.

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

Zapisz następujący blok kodu jako 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))"
    }
}

Zapisz następujący blok kodu jako 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()

Napiwek

Moduł główny dodaje typy niestandardowe do akceleratorów typów programu PowerShell. Ten wzorzec umożliwia użytkownikom modułów natychmiastowe uzyskiwanie dostępu do funkcji IntelliSense i autouzupełniania dla typów niestandardowych bez konieczności uprzedniego używania instrukcji using module .

Aby uzyskać więcej informacji na temat tego wzorca, zobacz sekcję "Eksportowanie z akceleratorami typów" about_Classes.

Zaimportuj moduł i sprawdź dane wyjściowe.

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

Moduł ładuje się bez błędów, ponieważ klasa InventoryItem jest zdefiniowana w innym pliku modułu niż klasa Inventory . Obie klasy są dostępne dla użytkowników modułów.

Dziedziczenie klasy bazowej

Gdy klasa dziedziczy z klasy bazowej, dziedziczy właściwości i metody klasy bazowej. Konstruktory klas bazowych nie dziedziczą bezpośrednio, ale mogą je wywoływać.

Jeśli klasa bazowa jest zdefiniowana na platformie .NET, a nie w programie PowerShell, należy pamiętać, że:

  • Klasy programu PowerShell nie mogą dziedziczyć z klas zapieczętowanych.
  • W przypadku dziedziczenia z ogólnej klasy bazowej parametr typu dla klasy ogólnej nie może być klasą pochodną. Użycie klasy pochodnej jako parametru typu zgłasza błąd analizy.

Aby zobaczyć, jak działa dziedziczenie i zastępowanie dla klas pochodnych, zobacz Przykład 1.

Konstruktory klas pochodnych

Klasy pochodne nie dziedziczą bezpośrednio konstruktorów klasy bazowej. Jeśli klasa bazowa definiuje konstruktor domyślny, a klasa pochodna nie definiuje żadnych konstruktorów, nowe wystąpienia klasy pochodnej używają konstruktora domyślnego klasy bazowej. Jeśli klasa bazowa nie definiuje konstruktora domyślnego, klasa pochodna musi jawnie zdefiniować co najmniej jeden konstruktor.

Konstruktory klasy pochodnej mogą wywoływać konstruktor z klasy bazowej za pomocą słowa kluczowego base . Jeśli klasa pochodna nie wywołuje jawnie konstruktora z klasy bazowej, wywołuje zamiast tego konstruktor domyślny dla klasy bazowej.

Aby wywołać konstruktor podstawowy bez definicji, dodaj : base(<parameters>) po parametrach konstruktora i przed blokiem treści.

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

Podczas definiowania konstruktora, który wywołuje konstruktor klasy bazowej, parametry mogą być dowolnym z następujących elementów:

  • Zmienna dowolnego parametru w konstruktorze klasy pochodnej.
  • Dowolna wartość statyczna.
  • Dowolne wyrażenie, które oblicza wartość typu parametru.

Klasa Ilustracja w przykładzie 1 pokazuje, jak klasa pochodna może używać konstruktorów klasy bazowej.

Metody klasy pochodnej

Gdy klasa pochodzi z klasy bazowej, dziedziczy metody klasy bazowej i ich przeciążenia. Wszystkie przeciążenia metody zdefiniowane w klasie bazowej, w tym ukryte metody, są dostępne w klasie pochodnej.

Klasa pochodna może zastąpić przeciążenie dziedziczonej metody przez ponowne zdefiniowanie jej w definicji klasy. Aby zastąpić przeciążenie, typy parametrów muszą być takie same jak w przypadku klasy bazowej. Typ danych wyjściowych przeciążenia może być inny.

W przeciwieństwie do konstruktorów metody nie mogą używać : base(<parameters>) składni do wywoływania przeciążenia klasy bazowej dla metody . Ponownie zdefiniowane przeciążenie klasy pochodnej całkowicie zastępuje przeciążenie zdefiniowane przez klasę bazową. Aby wywołać metodę klasy bazowej dla wystąpienia, należy rzutować zmienną wystąpienia ($this) do klasy bazowej przed wywołaniem metody .

Poniższy fragment kodu pokazuje, jak klasa pochodna może wywołać metodę klasy bazowej.

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

Aby zapoznać się z rozszerzonym przykładem pokazującym, jak klasa pochodna może zastąpić dziedziczone metody, zobacz klasę Ilustracja w przykładzie 1.

Właściwości klasy pochodnej

Gdy klasa pochodzi z klasy bazowej, dziedziczy właściwości klasy bazowej. Wszystkie właściwości zdefiniowane w klasie bazowej, w tym ukryte właściwości, są dostępne w klasie pochodnej.

Klasa pochodna może zastąpić dziedziczona właściwość, ponownie definiując ją w definicji klasy. Właściwość klasy pochodnej używa ponownie zdefiniowanego typu i wartości domyślnej, jeśli istnieje. Jeśli dziedziczona właściwość zdefiniowała wartość domyślną, a właściwość ponownie zdefiniowana nie, dziedziczona właściwość nie ma wartości domyślnej.

Jeśli klasa pochodna nie zastępuje właściwości statycznej, uzyskiwanie dostępu do właściwości statycznej za pośrednictwem klasy pochodnej uzyskuje dostęp do właściwości statycznej klasy bazowej. Modyfikowanie wartości właściwości za pomocą klasy pochodnej modyfikuje wartość w klasie bazowej. Każda inna klasa pochodna, która nie zastępuje właściwości statycznej, używa również wartości właściwości w klasie bazowej. Aktualizowanie wartości odziedziczonej właściwości statycznej w klasie, która nie zastępuje właściwości, może mieć niezamierzone efekty dla klas pochodnych z tej samej klasy bazowej.

W przykładzie 1 pokazano, jak klasy pochodne dziedziczą, rozszerzają i zastępują właściwości klasy bazowej.

Wyprowadzanie z typów ogólnych

Gdy klasa pochodzi z klasy ogólnej, parametr typu musi być już zdefiniowany przed przeanalizowaniem klasy pochodnej przez program PowerShell. Jeśli parametr typu dla klasy ogólnej jest klasą programu PowerShell lub wyliczeniem zdefiniowanym w tym samym pliku lub bloku kodu, program PowerShell zgłasza błąd.

Aby uzyskać klasę z ogólnej klasy bazowej z typem niestandardowym jako parametr typu, zdefiniuj klasę lub wyliczenie dla parametru typu w innym pliku lub module i użyj using module instrukcji , aby załadować definicję typu.

Przykład pokazujący, jak dziedziczyć z ogólnej klasy bazowej, zobacz Przykład 3.

Przydatne klasy do dziedziczenia

Istnieje kilka klas, które mogą być przydatne do dziedziczenia podczas tworzenia modułów programu PowerShell. W tej sekcji wymieniono kilka klas bazowych i elementy, dla których można użyć klasy pochodnej.

  • System.Attribute — klasy pochodne definiujące atrybuty, które mogą być używane dla zmiennych, parametrów, klas i definicji wyliczenia i nie tylko.
  • System.Management.Automation.ArgumentTransformationAttribute — klasy pochodne do obsługi konwersji danych wejściowych dla zmiennej lub parametru na określony typ danych.
  • System.Management.Automation.ValidateArgumentsAttribute — klasy pochodne, aby zastosować walidację niestandardową do zmiennych, parametrów i właściwości klas.
  • System.Collections.Generic.List — klasy pochodne ułatwiające tworzenie list określonego typu danych i zarządzanie nimi.
  • System.Exception — klasy pochodne służące do definiowania błędów niestandardowych.

Implementowanie interfejsów

Klasa programu PowerShell, która implementuje interfejs, musi implementować wszystkie elementy członkowskie tego interfejsu. Pominięcie elementów członkowskich interfejsu implementacji powoduje błąd analizy w czasie wykonywania skryptu.

Uwaga

Program PowerShell nie obsługuje deklarowania nowych interfejsów w skryscie programu PowerShell. Zamiast tego interfejsy muszą być zadeklarowane w kodzie platformy .NET i dodane do sesji za pomocą Add-Type polecenia cmdlet lub using assembly instrukcji .

Gdy klasa implementuje interfejs, może być używana jak każda inna klasa, która implementuje ten interfejs. Niektóre polecenia i operacje ograniczają obsługiwane typy do klas implementujących określony interfejs.

Aby przejrzeć przykładową implementację interfejsów, zobacz Przykład 2.

Przydatne interfejsy do zaimplementowania

Istnieje kilka klas interfejsów, które mogą być przydatne do dziedziczenia podczas tworzenia modułów programu PowerShell. W tej sekcji wymieniono kilka klas bazowych i elementy, dla których można użyć klasy pochodnej.

  • System.IEquatable — ten interfejs umożliwia użytkownikom porównywanie dwóch wystąpień klasy. Jeśli klasa nie implementuje tego interfejsu, program PowerShell sprawdza równoważność między dwoma wystąpieniami przy użyciu równości odwołań. Innymi słowy, wystąpienie klasy równa się tylko samemu, nawet jeśli wartości właściwości w dwóch wystąpieniach są takie same.
  • System.IComparable — ten interfejs umożliwia użytkownikom porównywanie wystąpień klasy z -leoperatorami porównania , -lt, -gei -gt . Jeśli klasa nie implementuje tego interfejsu, te operatory zgłaszają błąd.
  • System.IFormattable — ten interfejs umożliwia użytkownikom formatowanie wystąpień klasy w różnych ciągach. Jest to przydatne w przypadku klas, które mają więcej niż jedną standardową reprezentację ciągu, taką jak elementy budżetu, bibliografie i temperatury.
  • System.IConvertible — ten interfejs umożliwia użytkownikom konwertowanie wystąpień klasy na inne typy środowiska uruchomieniowego. Jest to przydatne w przypadku klas, które mają podstawową wartość liczbową lub można je przekonwertować na jedną.

Ograniczenia

  • Program PowerShell nie obsługuje definiowania interfejsów w kodzie skryptu.

    Obejście: Zdefiniuj interfejsy w języku C# i odwołuj się do zestawu, który definiuje interfejsy.

  • Klasy programu PowerShell mogą dziedziczyć tylko z jednej klasy bazowej.

    Obejście: Dziedziczenie klas jest przechodnie. Klasa pochodna może dziedziczyć z innej klasy pochodnej, aby uzyskać właściwości i metody klasy bazowej.

  • Podczas dziedziczenia z klasy ogólnej lub interfejsu należy już zdefiniować parametr typu dla klasy ogólnej. Klasa nie może zdefiniować się jako parametr typu dla klasy lub interfejsu.

    Obejście: Aby pochodzić z ogólnej klasy bazowej lub interfejsu, zdefiniuj typ niestandardowy w innym .psm1 pliku i użyj using module instrukcji , aby załadować typ. Nie ma obejścia dla typu niestandardowego, aby używać go jako parametru typu podczas dziedziczenia po typie ogólnym.

Zobacz też