Condividi tramite


informazioni_sulle_Classi_Ereditarietà

Breve descrizione

Viene descritto come definire classi che estendono altri tipi.

Descrizione lunga

Le classi di PowerShell supportano ereditarietà, che consente di definire una classe figlia che riutilizza (eredita), estende o modifica il comportamento di una classe padre. La classe i cui membri vengono ereditati viene chiamata classe base . La classe che eredita i membri della classe base viene chiamata classe derivata .

PowerShell supporta solo l'ereditarietà singola. Una classe può ereditare solo da una singola classe. Tuttavia, l'ereditarietà è transitiva, che consente di definire una gerarchia di ereditarietà per un set di tipi. In altre parole, il tipo D può ereditare dal tipo C, che eredita dal tipo B, che eredita dal tipo di classe base A. Poiché l'ereditarietà è transitiva, i membri del tipo A sono disponibili per il tipo D.

Le classi derivate non ereditano tutti i membri della classe base. I membri seguenti non vengono ereditati:

  • Costruttori statici, che inizializzano i dati statici di una classe.
  • Costruttori di istanza, chiamati per creare una nuova istanza della classe . Ogni classe deve definire i propri costruttori.

È possibile estendere una classe creando una nuova classe che deriva da una classe esistente. La classe derivata eredita le proprietà e i metodi della classe base. È possibile aggiungere o eseguire l'override dei membri della classe base in base alle esigenze.

Le classi possono anche ereditare da interfacce, che definiscono un contratto. Una classe che eredita da un'interfaccia deve implementare tale contratto. Quando lo fa, la classe è utilizzabile come qualsiasi altra classe che implementa tale interfaccia. Se una classe eredita da un'interfaccia ma non implementa l'interfaccia, PowerShell genera un errore di analisi per la classe .

Alcuni operatori di PowerShell dipendono da una classe che implementa un'interfaccia specifica. Ad esempio, l'operatore controlla solo l'uguaglianza dei riferimenti a meno che la classe implementi l'interfaccia system.IEquatable . Gli operatori -le, -lt, -gee -gt funzionano solo su classi che implementano l'interfaccia System.IComparable.

Una classe derivata usa la sintassi : per estendere una classe base o implementare interfacce. La classe derivata deve essere sempre all'estrema sinistra nella dichiarazione di classe.

In questo esempio viene illustrata la sintassi di ereditarietà della classe PowerShell di base.

class Derived : Base {...}

Questo esempio mostra l'ereditarietà con una dichiarazione di interfaccia successiva alla classe base.

class Derived : Base, Interface {...}

Sintassi

L'ereditarietà della classe usa le sintassi seguenti:

Sintassi di una riga

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

Per esempio:

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

Sintassi multilinea

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

Per esempio:

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

Esempi

Esempio 1 - Ereditarietà e override di una classe base

Nell'esempio seguente viene illustrato il comportamento delle proprietà ereditate con e senza eseguire l'override. Eseguire i blocchi di codice in ordine dopo aver letto la descrizione.

Definizione della classe base

Il primo blocco di codice definisce PublishedWork come classe base. Dispone di due proprietà statiche: List e Artists. Successivamente, definisce il metodo RegisterWork() statico per aggiungere opere alla proprietà statica List e gli artisti alla proprietà Artists, scrivendo un messaggio per ogni nuova voce negli elenchi.

La classe definisce tre proprietà di istanza che descrivono un lavoro pubblicato. Definisce infine i metodi di istanza di Register() e ToString().

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

Definizione di una classe derivata senza override

La prima classe derivata è Album. Non esegue l'override di alcuna proprietà o metodo. Aggiunge una nuova proprietà dell'istanza, Genres, che non esiste nella classe base.

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

Il blocco di codice seguente mostra il comportamento della classe Album derivata. Prima di tutto, imposta il $VerbosePreference in modo che i messaggi dei metodi della classe vengano inviati alla console. Crea tre istanze della classe, le mostra in una tabella e quindi le registra con il metodo statico RegisterWork() ereditato. Chiama quindi direttamente lo stesso metodo statico sulla classe base.

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

Si noti che anche se la classe album non ha definito un valore per Category o qualsiasi costruttore, la proprietà è stata definita dal costruttore predefinito della classe base.

Nella messaggistica verbosa, la seconda chiamata al metodo RegisterWork() riporta che le opere e gli artisti sono già registrati. Anche se la prima chiamata a RegisterWork() era per la classe derivata Album , utilizzava il metodo statico ereditato dalla classe base PublishedWork. Questo metodo ha aggiornato le proprietà statiche List e Artist nella classe base, su cui la classe derivata non ha eseguito l'override.

Il blocco di codice successivo cancella il registro e chiama il metodo di istanza sugli oggetti 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

Il metodo di istanza negli oggetti album ha lo stesso effetto della chiamata del metodo statico nella classe derivata o di base.

Il blocco di codice seguente confronta le proprietà statiche per la classe di base e la classe derivata, mostrando che sono uguali.

[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

Definizione di una classe derivata con sovrascritture

Il blocco di codice successivo definisce la classe Illustration che eredita dalla classe di base PublishedWork. La nuova classe estende la classe base definendo la proprietà dell'istanza Medium con un valore predefinito di Unknown.

A differenza della classe Album derivata, Illustration esegue l'override delle proprietà e dei metodi seguenti:

  • Esegue l'override della proprietà statica Artists. La definizione è la stessa, ma la classe Illustrazione la dichiara direttamente.
  • Esegue l'override della proprietà di istanza della categoria , impostando il valore predefinito a Illustrations.
  • Esegue l'override del metodo di istanza ToString() in modo che la rappresentazione di stringa di un'illustrazione includa il supporto con cui è stato creato.

La classe definisce anche il metodo statico RegisterIllustration() per chiamare prima il metodo RegisterWork() della classe base e quindi aggiungere l'artista alla proprietà statica ridefinita Artists nella classe derivata.

Infine, la classe esegue l'override di tutti e tre i costruttori:

  1. Il costruttore predefinito è vuoto, ad eccezione di un messaggio dettagliato che indica che è stata creata un'illustrazione.
  2. Il costruttore successivo accetta due valori stringa per il nome e l'artista che ha creato l'illustrazione. Anziché implementare la logica per impostare le proprietà Name e Artist, il costruttore chiama il costruttore appropriato dalla classe base.
  3. L'ultimo costruttore accetta tre valori di stringa per il nome, l'artista e il medium dell'illustrazione. Entrambi i costruttori scrivono un messaggio dettagliato che indica che hanno creato un'illustrazione.
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"
    }
}

Il blocco di codice seguente illustra il comportamento della classe Illustrazione derivata. Crea tre istanze della classe, le mostra in una tabella e quindi le registra con il metodo statico RegisterWork() ereditato. Chiama quindi direttamente lo stesso metodo statico sulla classe base. Infine, scrive messaggi che mostrano l'elenco di artisti registrati per la classe base e la classe derivata.

$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

La messaggistica verbosa dall'operazione di creazione delle istanze mostra che:

  • Quando si crea la prima istanza, il costruttore predefinito della classe base è stato chiamato prima del costruttore predefinito della classe derivata.
  • Quando si crea la seconda istanza, il costruttore ereditato in modo esplicito è stato chiamato per la classe base prima del costruttore della classe derivata.
  • Quando si crea la terza istanza, il costruttore predefinito della classe base è stato chiamato prima del costruttore della classe derivata.

I messaggi dettagliati del metodo RegisterWork() indicano che le opere e gli artisti sono già stati registrati. Questo è dovuto al fatto che il metodo RegisterIllustration() invoca internamente il metodo RegisterWork().

Tuttavia, quando si confronta il valore della proprietà static Artist sia per la classe di base che per la classe derivata, i valori sono diversi. La proprietà Artisti per la classe derivata include solo illustratori, non gli artisti dell'album. La ridefinizione della proprietà Artist nella classe derivata impedisce alla classe di restituire la proprietà statica nella classe di base.

Il blocco di codice finale chiama il metodo sugli elementi della proprietà statica List nella classe di base.

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

Le istanze di Album restituiscono solo il nome e l'artista nella stringa. Le istanze di Illustrazione includevano anche il medium tra parentesi, perché tale classe sovrascriveva il metodo ToString().

Esempio 2 - Implementazione di interfacce

Nell'esempio seguente viene illustrato come una classe può implementare una o più interfacce. L'esempio estende la definizione di una classe di Temperature per supportare più operazioni e comportamenti.

Definizione della classe iniziale

Prima di implementare qualsiasi interfaccia, la classe Temperature viene definita con due proprietà, Gradi e Scala. Vengono definiti costruttori e tre metodi di istanza per restituire il valore dell'istanza come gradi di una determinata scala.

La classe definisce le scale disponibili con l'enumerazione 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
}

In questa implementazione di base, tuttavia, esistono alcune limitazioni, come illustrato nell'output di esempio seguente:

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

L'output mostra che le istanze di Temperature:

  • Non vengono mostrate correttamente come stringhe.
  • Non è possibile verificare correttamente l'equivalenza.
  • Non è possibile confrontare.

Questi tre problemi possono essere risolti implementando interfacce per la classe .

Implementazione di IFormattable

La prima interfaccia da implementare per la classe Temperature è System.IFormattable. Questa interfaccia abilita la formattazione di un'istanza della classe come stringhe diverse. Per implementare l'interfaccia, la classe deve ereditare da System.IFormattable e definire il metodo di istanza ToString().

Il metodo di istanza di ToString() deve avere la firma seguente:

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

La firma richiesta dall'interfaccia è elencata nella documentazione di riferimento .

Per Temperature, la classe deve supportare tre formati: C per restituire l'istanza in Celsius, F per restituirla in Fahrenheit e K per restituirla in Kelvin. Per qualsiasi altro formato, il metodo deve generare un 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'"
    )
}

In questa implementazione, per impostazione predefinita, il metodo utilizza la scala dell'istanza per il formato e la cultura corrente durante la formattazione del valore numerico del grado. Usa i metodi di istanza To<Scale>() per convertire i gradi, formattarli in posizioni decimali e accoda il simbolo di grado appropriato alla stringa.

Con la firma necessaria implementata, la classe può anche definire sovraccarichi per semplificare la restituzione dell'istanza formattata.

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

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

Il codice seguente illustra la definizione aggiornata per 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
}

L'output delle sovraccaricature del metodo è illustrato nel blocco seguente.

$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

Implementazione di IEquatable

Ora che la classe temperature può essere formattata per garantire la leggibilità, gli utenti devono essere in grado di verificare se due istanze della classe sono uguali. Per supportare questo test, la classe deve implementare l'interfaccia System.IEquatable .

Per implementare l'interfaccia, la classe deve ereditare da System.IEquatable e definire il metodo di istanza Equals(). Il metodo Equals() deve avere la firma seguente:

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

La firma richiesta dall'interfaccia è elencata nella documentazione di riferimento .

Per Temperature, la classe deve supportare solo il confronto di due istanze della classe . Per qualsiasi altro valore o tipo, incluso $null, deve restituire $false. Quando si confrontano due temperature, il metodo deve convertire entrambi i valori in Kelvin, poiché le temperature possono essere equivalenti anche con scale diverse.

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

Con il metodo di interfaccia implementato, la definizione aggiornata per 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
}

Il blocco seguente illustra il comportamento della classe aggiornata:

$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

Implementazione di IComparable

L'ultima interfaccia da implementare per la classe Temperature è System.IComparable. Quando la classe implementa questa interfaccia, gli utenti possono usare gli operatori -lt, -le, -gte -ge per confrontare le istanze della classe .

Per implementare l'interfaccia, la classe deve ereditare da System.IComparable e definire il metodo di istanza Equals(). Il metodo Equals() deve avere la firma seguente:

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

La firma richiesta dall'interfaccia è elencata nella documentazione di riferimento .

Per Temperature, la classe deve supportare solo il confronto di due istanze della classe . Poiché il tipo sottostante per la proprietà gradi, anche quando viene convertito in una scala diversa, è un numero a virgola mobile, il metodo può fare affidamento sul tipo sottostante per il confronto effettivo.

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

La definizione finale per la classe Temperatura è:

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
}

Con la definizione completa, gli utenti possono formattare e confrontare istanze della classe in PowerShell come qualsiasi tipo predefinito.

$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

Esempio 3 - Eredità da una classe base generica

In questo esempio viene illustrato come derivare da un tipo generico purché il parametro di tipo sia già definito in fase di analisi.

Uso di una classe predefinita come parametro di tipo

Eseguire il blocco di codice seguente. Mostra come una nuova classe può ereditare da un tipo generico purché il parametro di tipo sia già definito in fase di analisi.

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

Uso di una classe personalizzata come parametro di tipo

Il blocco di codice successivo definisce prima una nuova classe, ExampleItem, con una singola proprietà dell'istanza e il metodo ToString(). Definisce quindi la classe ExampleItemList che eredita dalla classe di base System.Collections.Generic.List con ExampleItem come parametro di tipo.

Copiare l'intero blocco di codice ed eseguirlo come singola istruzione.

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

L'esecuzione dell'intero blocco di codice genera un errore perché PowerShell non ha ancora caricato la classe ExampleItem nel runtime. Non è ancora possibile usare il nome della classe come parametro di tipo per la classe base System.Collections.Generic.List.

Eseguire i blocchi di codice seguenti nell'ordine in cui sono definiti.

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

Questa volta, PowerShell non genera errori. Entrambe le classi sono ora definite. Eseguire il blocco di codice seguente per visualizzare il comportamento della nuova classe.

$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

Derivazione di un generico con un parametro di tipo personalizzato in un modulo

I blocchi di codice seguenti illustrano come definire una classe che eredita da una classe di base generica che usa un tipo personalizzato per il parametro di tipo.

Salvare il blocco di codice seguente come GenericExample.psd1.

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

Salvare il blocco di codice seguente come 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))"
    }
}

Salvare il blocco di codice seguente come 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()

Mancia

Il modulo radice aggiunge i tipi personalizzati agli acceleratori di tipi di PowerShell. Questo modello consente agli utenti del modulo di accedere immediatamente a IntelliSense e al completamento automatico per i tipi personalizzati senza dover usare prima l'istruzione using module.

Per altre informazioni su questo modello, vedere la sezione "Esportazione con acceleratori di tipi" di about_Classes.

Importare il modulo e verificare l'output.

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

Il modulo viene caricato senza errori perché la classe InventoryItem è definita in un file di modulo diverso rispetto alla classe Inventory. Entrambe le classi sono disponibili per gli utenti del modulo.

Ereditarietà di una classe base

Quando una classe eredita da una classe di base, eredita le proprietà e i metodi della classe base. Non eredita direttamente i costruttori della classe di base, ma può chiamarli.

Quando la classe base è definita in .NET anziché In PowerShell, tenere presente che:

  • Le classi di PowerShell non possono ereditare da classi sigillate.
  • Quando eredita da una classe di base generica, il parametro di tipo per la classe generica non può essere la classe derivata. L'uso della classe derivata come parametro di tipo genera un errore di analisi.

Per informazioni sul funzionamento dell'ereditarietà e dell'override per le classi derivate, vedere esempio 1.

Costruttori di classi derivate

Le classi derivate non ereditano direttamente i costruttori della classe base. Se la classe base definisce un costruttore predefinito e la classe derivata non definisce costruttori, le nuove istanze della classe derivata usano il costruttore predefinito della classe base. Se la classe base non definisce un costruttore predefinito, la classe derivata deve definire in modo esplicito almeno un costruttore.

I costruttori di classi derivate possono richiamare un costruttore dalla classe base con la parola chiave base. Se la classe derivata non richiama in modo esplicito un costruttore dalla classe base, richiama invece il costruttore predefinito per la classe base.

Per richiamare un costruttore di base non predefinito, aggiungere : base(<parameters>) dopo i parametri del costruttore e prima del blocco del corpo.

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

Quando si definisce un costruttore che chiama un costruttore di classe base, i parametri possono essere uno degli elementi seguenti:

  • Variabile di qualsiasi parametro nel costruttore della classe derivata.
  • Qualsiasi valore statico.
  • Qualsiasi espressione che restituisce un valore del tipo di parametro.

La classe di Illustrazione in Esempio 1 mostra come una classe derivata può utilizzare i costruttori della classe base.

Metodi della classe derivata

Quando una classe deriva da una classe di base, eredita i metodi della classe di base e i relativi overload. Tutti gli overload di metodo definiti nella classe base, inclusi i metodi nascosti, sono disponibili nella classe derivata.

Una classe derivata può eseguire l'override di un overload di metodo ereditato ridefinendolo nella definizione della classe. Per eseguire l'override dell'overload, i tipi di parametro devono essere uguali a quello della classe base. Il tipo di output per l'overload può essere diverso.

A differenza dei costruttori, i metodi non possono usare la sintassi : base(<parameters>) per richiamare un overload della classe di base per il metodo . L'overload ridefinito nella classe derivata sostituisce completamente l'overload definito dalla classe base. Per chiamare il metodo della classe base per un'istanza, eseguire il cast della variabile di istanza ($this) alla classe base prima di chiamare il metodo.

Il frammento di codice seguente mostra come una classe derivata può chiamare il metodo della classe base.

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

Per un esempio esteso che illustra come una classe derivata può eseguire l'override dei metodi ereditati, vedere la classe Illustrazione nell'esempio 1.

Proprietà della classe derivata

Quando una classe deriva da una classe di base, eredita le proprietà della classe base. Tutte le proprietà definite nella classe base, incluse le proprietà nascoste, sono disponibili nella classe derivata.

Una classe derivata può eseguire l'override di una proprietà ereditata definendola nella definizione della classe. La proprietà nella classe derivata utilizza il tipo ridefinito e il valore predefinito, se presenti. Se la proprietà ereditata ha definito un valore predefinito e la proprietà ridefinita non lo fa, la proprietà ereditata non ha alcun valore predefinito.

Se una classe derivata non esegue l'override di una proprietà statica, l'accesso alla proprietà statica tramite la classe derivata accede alla proprietà statica della classe base. La modifica del valore della proprietà tramite la classe derivata modifica il valore nella classe base. Qualsiasi altra classe derivata che non esegue l'override della proprietà statica usa anche il valore della proprietà nella classe di base. L'aggiornamento del valore di una proprietà statica ereditata in una classe che non esegue l'override della proprietà potrebbe avere effetti imprevisti per le classi derivate dalla stessa classe di base.

esempio 1 illustra come le classi derivate che ereditano, estendono ed eseguono l'override delle proprietà della classe di base.

Derivazione da generici

Quando una classe deriva da un oggetto generico, il parametro di tipo deve essere già definito prima che PowerShell analizzi la classe derivata. Se il parametro di tipo per il generico è una classe di PowerShell o un'enumerazione definita nello stesso file o blocco di codice, PowerShell genera un errore.

Per derivare una classe da una classe base generica con un tipo personalizzato come parametro di tipo, definire la classe o l'enumerazione per il parametro di tipo in un file o modulo diverso e usare l'istruzione using module per caricare la definizione del tipo.

Per un esempio che illustra come ereditare da una classe di base generica, vedere esempio 3.

Classi utili da ereditare

Esistono alcune classi che possono essere utili per ereditare durante la creazione di moduli di PowerShell. In questa sezione sono elencate alcune classi di base e le classi per cui è possibile usare una classe derivata.

  • System.Attribute: derivare classi per definire attributi che possono essere usati per variabili, parametri, definizioni di classe ed enumerazione e altro ancora.
  • System.Management.Automation.ArgumentTransformationAttribute - Derivare le classi per gestire la conversione dell'input per una variabile o un parametro in un tipo di dati specifico.
  • System.Management.Automation.ValidateArgumentsAttribute - Derivare classi per applicare la convalida personalizzata a variabili, parametri e proprietà di classe.
  • System.Collections.Generic.List - Derivare classi per semplificare la creazione e la gestione di elenchi di un tipo di dati specifico.
  • System.Exception - Derivare classi per definire errori personalizzati.

Implementazione di interfacce

Una classe di PowerShell che implementa un'interfaccia deve implementare tutti i membri di tale interfaccia. L'omissione dei membri dell'interfaccia di implementazione causa un errore in fase di analisi nello script.

Nota

PowerShell non supporta la dichiarazione di nuove interfacce nello script di PowerShell. Al contrario, le interfacce devono essere dichiarate nel codice .NET e aggiunte alla sessione con il cmdlet Add-Type o l'istruzione using assembly.

Quando una classe implementa un'interfaccia, può essere usata come qualsiasi altra classe che implementa tale interfaccia. Alcuni comandi e operazioni limitano i tipi supportati alle classi che implementano un'interfaccia specifica.

Per esaminare un'implementazione di esempio di interfacce, vedere esempio 2.

Interfacce utili da implementare

Esistono alcune classi di interfaccia che possono essere utili per ereditare durante la creazione di moduli di PowerShell. In questa sezione sono elencate alcune classi di base e le classi per cui è possibile usare una classe derivata.

  • System.IEquatable: questa interfaccia consente agli utenti di confrontare due istanze della classe. Quando una classe non implementa questa interfaccia, PowerShell verifica la parità tra due istanze usando l'uguaglianza dei riferimenti. In altre parole, un'istanza della classe è uguale solo a se stessa, anche se i valori delle proprietà in due istanze sono uguali.
  • System.IComparable: questa interfaccia consente agli utenti di confrontare le istanze della classe con gli operatori di confronto -le, -lt, -gee -gt. Quando una classe non implementa questa interfaccia, tali operatori generano un errore.
  • System.IFormattable: questa interfaccia consente agli utenti di formattare le istanze della classe in stringhe diverse. Ciò è utile per le classi con più di una rappresentazione di stringa standard, ad esempio articoli di budget, bibliografie e temperature.
  • System.IConvertible: questa interfaccia consente agli utenti di convertire istanze della classe in altri tipi di runtime. Ciò è utile per le classi che hanno un valore numerico sottostante o possono essere convertite in una classe.

Limitazioni

  • PowerShell non supporta la definizione di interfacce nel codice script.

    Soluzione alternativa: definire le interfacce in C# e fare riferimento all'assembly che definisce le interfacce.

  • Le classi di PowerShell possono ereditare solo da una classe di base.

    Soluzione alternativa: l'ereditarietà della classe è transitiva. Una classe derivata può ereditare da un'altra classe derivata per ottenere le proprietà e i metodi di una classe base.

  • Quando eredita da una classe o un'interfaccia generica, il parametro di tipo per il generico deve essere già definito. Una classe non può definirsi come parametro di tipo per una classe o un'interfaccia.

    Soluzione alternativa: per derivare da una classe o un'interfaccia di base generica, definire il tipo personalizzato in un file di .psm1 diverso e usare l'istruzione using module per caricare il tipo. Non c'è soluzione per un tipo personalizzato che usi sé stesso come parametro di tipo quando eredita da un generico.

Vedere anche