Partager via


about_Classes_Inheritance

Description courte

Décrit comment définir des classes qui étendent d’autres types.

Description longue

Les classes PowerShell prennent en charge l’héritage, ce qui vous permet de définir une classe enfant qui réutilise (hérite), étend ou modifie le comportement d’une classe parente. La classe dont les membres sont hérités s’appelle la classe de base. La classe qui hérite des membres de la classe de base est appelée la classe dérivée.

PowerShell prend uniquement en charge l’héritage unique. Une classe ne peut hériter qu’d’une seule classe. Toutefois, l’héritage est transitif, ce qui permet de définir une hiérarchie d’héritage pour un ensemble de types. En d’autres termes, le type D peut hériter du type C, qui hérite du type B, qui hérite du type A de classe de base. Étant donné que l’héritage est transitif, les membres de type A sont disponibles pour le type D.

Les classes dérivées n’héritent pas de tous les membres de la classe de base. Les membres suivants ne sont pas hérités :

  • Les constructeurs statiques, qui initialisent les données statiques d’une classe.
  • Les Constructeurs d’instance, que vous appelez pour créer une nouvelle instance de la classe. Chaque classe doit définir ses propres constructeurs.

Vous pouvez étendre une classe en créant une classe qui dérive d’une classe existante. La classe dérivée hérite des propriétés et des méthodes de la classe de base. Vous pouvez ajouter ou remplacer les membres de classe de base en fonction des besoins.

Les classes peuvent également hériter d’interfaces, qui définissent un contrat. Une classe qui hérite d’une interface doit implémenter ce contrat. Dans ce cas, la classe est utilisable comme toute autre classe implémentant cette interface. Si une classe hérite d’une interface mais n’implémente pas l’interface, PowerShell génère une erreur d’analyse pour la classe.

Certains opérateurs PowerShell dépendent d’une classe implémentant une interface spécifique. Par exemple, l’opérateur -eq vérifie uniquement l’égalité de référence, sauf si la classe implémente l’interface System.IEquatable . Les -leopérateurs , et -gt les -lt-geopérateurs fonctionnent uniquement sur les classes qui implémentent l’interface System.IComparable.

Une classe dérivée utilise la : syntaxe pour étendre une classe de base ou implémenter des interfaces. La classe dérivée doit toujours être la plus à gauche dans la déclaration de classe.

Cet exemple montre la syntaxe d’héritage de classe PowerShell de base.

Class Derived : Base {...}

Cet exemple montre l’héritage avec une déclaration d’interface qui vient après la classe de base.

Class Derived : Base, Interface {...}

Syntaxe

L’héritage de classe utilise les syntaxes suivantes :

Syntaxe d’une ligne

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

Par exemple :

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

Syntaxe multiligne

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

Par exemple :

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

Exemples

Exemple 1 : hériter et remplacer d’une classe de base

L’exemple suivant montre le comportement des propriétés héritées avec et sans substitution. Exécutez les blocs de code dans l’ordre après avoir lu leur description.

Définition de la classe de base

Le premier bloc de code définit PublishedWork comme une classe de base. Il a deux propriétés statiques, List et Artists. Ensuite, il définit la méthode statique RegisterWork() pour ajouter des œuvres à la propriété Liste statique et aux artistes à la propriété Artists, en écrivant un message pour chaque nouvelle entrée dans les listes.

La classe définit trois propriétés d’instance qui décrivent un travail publié. Enfin, il définit les méthodes et ToString() les méthodes d’instanceRegister().

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

Définition d’une classe dérivée sans remplacement

La première classe dérivée est Album. Elle ne remplace aucune propriété ou méthode. Il ajoute une nouvelle propriété d’instance, Genres, qui n’existe pas sur la classe de base.

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

Le bloc de code suivant montre le comportement de la classe Album dérivée. Tout d’abord, il définit la $VerbosePreference façon dont les messages des méthodes de classe émettent dans la console. Il crée trois instances de la classe, les affiche dans une table, puis les inscrit avec la méthode statique RegisterWork() héritée. Il appelle ensuite la même méthode statique sur la classe de base directement.

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

Notez que même si la classe Album n’a pas défini de valeur pour Category ou tout constructeur, la propriété a été définie par le constructeur par défaut de la classe de base.

Dans la messagerie détaillée, le deuxième appel à la RegisterWork() méthode signale que les œuvres et les artistes sont déjà inscrits. Même si le premier appel à RegisterWork() était pour la classe Album dérivée, il a utilisé la méthode statique héritée de la classe PublishedWork de base. Cette méthode a mis à jour les propriétés static List et Artist sur la classe de base, que la classe dérivée n’a pas remplacée.

Le bloc de code suivant efface le Registre et appelle la Register() méthode d’instance sur les objets 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

La méthode d’instance sur les objets Album a le même effet que l’appel de la méthode statique sur la classe dérivée ou de base.

Le bloc de code suivant compare les propriétés statiques de la classe de base et de la classe dérivée, montrant qu’elles sont identiques.

[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

Définition d’une classe dérivée avec des remplacements

Le bloc de code suivant définit la classe Illustration qui hérite de la classe PublishedWork de base. La nouvelle classe étend la classe de base en définissant la propriété d’instance Medium avec la valeur Unknownpar défaut .

Contrairement à la classe Album dérivée, l’illustration remplace les propriétés et méthodes suivantes :

  • Il remplace la propriété Artistes statiques. La définition est la même, mais la classe Illustration la déclare directement.
  • Elle remplace la propriété d’instance Category , en définissant la valeur Illustrationspar défaut sur .
  • Il remplace la ToString() méthode d’instance afin que la représentation sous forme de chaîne d’une illustration inclut le support avec qui il a été créé.

La classe définit également la méthode statique RegisterIllustration() pour appeler d’abord la méthode de classe RegisterWork() de base, puis ajouter l’artiste à la propriété statique Artists substituée sur la classe dérivée.

Enfin, la classe remplace les trois constructeurs :

  1. Le constructeur par défaut est vide, à l’exception d’un message détaillé indiquant qu’il a créé une illustration.
  2. Le constructeur suivant prend deux valeurs de chaîne pour le nom et l’artiste qui a créé l’illustration. Au lieu d’implémenter la logique pour définir les propriétés Name et Artist , le constructeur appelle le constructeur approprié à partir de la classe de base.
  3. Le dernier constructeur prend trois valeurs de chaîne pour le nom, l’artiste et le support de l’illustration. Les deux constructeurs écrivent un message détaillé indiquant qu’ils ont créé une illustration.
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"
    }
}

Le bloc de code suivant montre le comportement de la classe Illustration dérivée. Il crée trois instances de la classe, les affiche dans une table, puis les inscrit avec la méthode statique RegisterWork() héritée. Il appelle ensuite la même méthode statique sur la classe de base directement. Enfin, il écrit des messages montrant la liste des artistes inscrits pour la classe de base et la classe dérivée.

$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 messagerie détaillée de la création des instances montre que :

  • Lors de la création de la première instance, le constructeur par défaut de la classe de base a été appelé avant le constructeur par défaut de classe dérivée.
  • Lors de la création de la deuxième instance, le constructeur hérité explicitement a été appelé pour la classe de base avant le constructeur de classe dérivée.
  • Lors de la création de la troisième instance, le constructeur par défaut de la classe de base a été appelé avant le constructeur de classe dérivée.

Les messages détaillés de la RegisterWork() méthode indiquent que les œuvres et les artistes ont déjà été enregistrés. Cela est dû au fait que la RegisterIllustration() méthode a appelé la RegisterWork() méthode en interne.

Toutefois, lors de la comparaison de la valeur de la propriété Artist statique pour la classe de base et la classe dérivée, les valeurs sont différentes. La propriété Artists pour la classe dérivée comprend uniquement les illustrateurs, et non les artistes de l’album. La définition de la propriété Artist dans la classe dérivée empêche la classe de retourner la propriété statique sur la classe de base.

Le bloc de code final appelle la ToString() méthode sur les entrées de la propriété List statique sur la classe de 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)

Les instances Album retournent uniquement le nom et l’artiste dans leur chaîne. Les instances d’illustration incluaient également le support entre parenthèses, car cette classe a dépassé la ToString() méthode.

Exemple 2 : implémentation d’interfaces

L’exemple suivant montre comment une classe peut implémenter une ou plusieurs interfaces. L’exemple étend la définition d’une classe Temperature pour prendre en charge davantage d’opérations et de comportements.

Définition de classe initiale

Avant d’implémenter des interfaces, la classe Temperature est définie avec deux propriétés, Degrees et Scale. Il définit les constructeurs et trois méthodes d’instance pour renvoyer l’instance en tant que degrés d’une échelle particulière.

La classe définit les échelles disponibles avec l’énumération 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
}

Toutefois, dans cette implémentation de base, il existe quelques limitations, comme illustré dans l’exemple de sortie suivant :

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

La sortie indique que les instances de Température :

  • Ne s’affichez pas correctement sous forme de chaînes.
  • Impossible de vérifier correctement l’équivalence.
  • Impossible de comparer.

Ces trois problèmes peuvent être résolus en implémentant des interfaces pour la classe.

Implémentation d’IFormattable

La première interface à implémenter pour la classe Temperature est System.IFormattable. Cette interface permet de mettre en forme une instance de la classe sous forme de chaînes différentes. Pour implémenter l’interface, la classe doit hériter de System.IFormattable et définir la méthode d’instance ToString() .

La ToString() méthode d’instance doit avoir la signature suivante :

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

La signature requise par l’interface est répertoriée dans la documentation de référence.

Pour Temperature, la classe doit prendre en charge trois formats : C pour retourner l’instance en Celsius, F pour la retourner dans Fahrenheit et K pour la retourner dans Kelvin. Pour tout autre format, la méthode doit lever une exception 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'"
    )
}

Dans cette implémentation, la méthode est définie par défaut sur l’échelle de l’instance pour le format et la culture actuelle lors de la mise en forme de la valeur numérique du degré lui-même. Il utilise les To<Scale>() méthodes d’instance pour convertir les degrés, les met en deux décimales et ajoute le symbole de degré approprié à la chaîne.

Avec la signature requise implémentée, la classe peut également définir des surcharges pour faciliter le retour de l’instance mise en forme.

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

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

Le code suivant montre la définition mise à jour de Température :

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
}

La sortie des surcharges de méthode s’affiche dans le bloc suivant.

$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

Implémentation d’IEquatable

Maintenant que la classe Temperature peut être mise en forme pour la lisibilité, les utilisateurs doivent pouvoir vérifier si deux instances de la classe sont égales. Pour prendre en charge ce test, la classe doit implémenter l’interface System.IEquatable .

Pour implémenter l’interface, la classe doit hériter de System.IEquatable et définir la Equals() méthode d’instance. La Equals() méthode doit avoir la signature suivante :

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

La signature requise par l’interface est répertoriée dans la documentation de référence.

Pour Temperature, la classe ne doit prendre en charge que la comparaison de deux instances de la classe. Pour toute autre valeur ou type, y compris $null, il doit retourner $false. Lors de la comparaison de deux températures, la méthode doit convertir les deux valeurs en Kelvin, car les températures peuvent être équivalentes même avec des échelles différentes.

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

Avec la méthode d’interface implémentée, la définition mise à jour de Temperature est la suivante :

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
}

Le bloc suivant montre comment la classe mise à jour se comporte :

$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

Implémentation d’IComparable

La dernière interface à implémenter pour la classe Temperature est System.IComparable. Lorsque la classe implémente cette interface, les utilisateurs peuvent utiliser les opérateurs , -leet -gt-ge les -ltopérateurs pour comparer les instances de la classe.

Pour implémenter l’interface, la classe doit hériter de System.IComparable et définir la méthode d’instance Equals() . La Equals() méthode doit avoir la signature suivante :

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

La signature requise par l’interface est répertoriée dans la documentation de référence.

Pour Temperature, la classe ne doit prendre en charge que la comparaison de deux instances de la classe. Étant donné que le type sous-jacent de la propriété Degrees , même lorsqu’il est converti à une échelle différente, est un nombre à virgule flottante, la méthode peut s’appuyer sur le type sous-jacent pour la comparaison réelle.

[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 définition finale de la classe Temperature est la suivante :

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
}

Avec la définition complète, les utilisateurs peuvent mettre en forme et comparer des instances de la classe dans PowerShell comme n’importe quel type intégré.

$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

Exemple 3 : héritage d’une classe de base générique

Cet exemple montre comment dériver d’une classe générique telle que System.Collections.Generic.List.

Utilisation d’une classe intégrée comme paramètre de type

Exécutez le bloc de code suivant. Il montre comment une nouvelle classe peut hériter d’un type générique tant que le paramètre de type est déjà défini au moment de l’analyse.

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

Utilisation d’une classe personnalisée comme paramètre de type

Le bloc de code suivant définit d’abord une nouvelle classe, ExampleItem, avec une propriété d’instance unique et la ToString() méthode. Ensuite, il définit la classe ExampleItemList qui hérite de la classe de base System.Collections.Generic.List avec ExampleItem comme paramètre de type.

Copiez l’intégralité du bloc de code et exécutez-le en tant qu’instruction unique.

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’exécution de l’intégralité du bloc de code génère une erreur, car PowerShell n’a pas encore chargé la classe ExampleItem dans le runtime. Vous ne pouvez pas utiliser le nom de classe comme paramètre de type pour la classe de base System.Collections.Generic.List .

Exécutez les blocs de code suivants dans l’ordre dans lequel ils sont définis.

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

Cette fois, PowerShell ne génère aucune erreur. Les deux classes sont maintenant définies. Exécutez le bloc de code suivant pour afficher le comportement de la nouvelle 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

Dérivation d’un générique avec un paramètre de type personnalisé dans un module

Les blocs de code suivants montrent comment définir une classe qui hérite d’une classe de base générique qui utilise un type personnalisé pour le paramètre de type.

Enregistrez le bloc de code suivant sous GenericExample.psd1.

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

Enregistrez le bloc de code suivant sous 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))"
    }
}

Enregistrez le bloc de code suivant sous 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()

Conseil

Le module racine ajoute les types personnalisés aux accélérateurs de type de PowerShell. Ce modèle permet aux utilisateurs du module d’accéder immédiatement à IntelliSense et à la saisie semi-automatique pour les types personnalisés sans avoir à utiliser d’abord l’instruction using module .

Pour plus d’informations sur ce modèle, consultez la section « Exportation avec accélérateurs de type » de about_Classes.

Importez le module et vérifiez la sortie.

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

Le module se charge sans erreur, car la classe InventoryItem est définie dans un fichier de module différent de la classe Inventory . Les deux classes sont disponibles pour les utilisateurs du module.

Héritage d’une classe de base

Lorsqu’une classe hérite d’une classe de base, elle hérite des propriétés et des méthodes de la classe de base. Il n’hérite pas directement des constructeurs de classe de base, mais il peut les appeler.

Lorsque la classe de base est définie dans .NET plutôt que dans PowerShell, notez que :

  • Les classes PowerShell ne peuvent pas hériter des classes scellées.
  • Lors de l’héritage d’une classe de base générique, le paramètre de type de la classe générique ne peut pas être la classe dérivée. L’utilisation de la classe dérivée comme paramètre de type déclenche une erreur d’analyse.

Pour voir comment l’héritage et la substitution fonctionnent pour les classes dérivées, consultez l’exemple 1.

Constructeurs de classes dérivées

Les classes dérivées n’héritent pas directement des constructeurs de la classe de base. Si la classe de base définit un constructeur par défaut et que la classe dérivée ne définit aucun constructeur, les nouvelles instances de la classe dérivée utilisent le constructeur par défaut de la classe de base. Si la classe de base ne définit pas de constructeur par défaut, la classe dérivée doit définir explicitement au moins un constructeur.

Les constructeurs de classes dérivées peuvent appeler un constructeur de la classe de base avec le base mot clé. Si la classe dérivée n’appelle pas explicitement un constructeur de la classe de base, elle appelle le constructeur par défaut pour la classe de base à la place.

Pour appeler un constructeur de base nondefault, ajoutez : base(<parameters>) après les paramètres du constructeur et avant le bloc de corps.

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

Lors de la définition d’un constructeur qui appelle un constructeur de classe de base, les paramètres peuvent être l’un des éléments suivants :

  • Variable de n’importe quel paramètre sur le constructeur de classe dérivé.
  • Toute valeur statique.
  • Toute expression qui prend la valeur d’une valeur du type de paramètre.

La classe Illustration de l’exemple 1 montre comment une classe dérivée peut utiliser les constructeurs de classe de base.

Méthodes de classe dérivées

Lorsqu’une classe dérive d’une classe de base, elle hérite des méthodes de la classe de base et de leurs surcharges. Toutes les surcharges de méthode définies sur la classe de base, y compris les méthodes masquées, sont disponibles sur la classe dérivée.

Une classe dérivée peut remplacer une surcharge de méthode héritée en la redéfinissant dans la définition de classe. Pour remplacer la surcharge, les types de paramètres doivent être identiques à ceux de la classe de base. Le type de sortie de la surcharge peut être différent.

Contrairement aux constructeurs, les méthodes ne peuvent pas utiliser la : base(<parameters>) syntaxe pour appeler une surcharge de classe de base pour la méthode. La surcharge redéfinie sur la classe dérivée remplace complètement la surcharge définie par la classe de base. Pour appeler la méthode de classe de base pour une instance, convertissez la variable d’instance ($this) en classe de base avant d’appeler la méthode.

L’extrait de code suivant montre comment une classe dérivée peut appeler la méthode de classe de 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

Pour obtenir un exemple étendu montrant comment une classe dérivée peut remplacer les méthodes héritées, consultez la classe Illustration dans l’exemple 1.

Propriétés de classe dérivées

Lorsqu’une classe dérive d’une classe de base, elle hérite des propriétés de la classe de base. Toutes les propriétés définies sur la classe de base, y compris les propriétés masquées, sont disponibles sur la classe dérivée.

Une classe dérivée peut remplacer une propriété héritée en la redéfinissant dans la définition de classe. La propriété de la classe dérivée utilise le type redéfini et la valeur par défaut, le cas échéant. Si la propriété héritée a défini une valeur par défaut et que la propriété redéfinie ne le fait pas, la propriété héritée n’a pas de valeur par défaut.

Si une classe dérivée ne remplace pas une propriété statique, l’accès à la propriété statique via la classe dérivée accède à la propriété statique de la classe de base. La modification de la valeur de propriété par le biais de la classe dérivée modifie la valeur sur la classe de base. Toute autre classe dérivée qui ne remplace pas la propriété statique utilise également la valeur de la propriété sur la classe de base. La mise à jour de la valeur d’une propriété statique héritée dans une classe qui ne remplace pas la propriété peut avoir des effets inattendus pour les classes dérivées de la même classe de base.

L’exemple 1 montre comment les classes dérivées qui héritent, étendent et remplacent les propriétés de classe de base.

Dérivation de génériques

Lorsqu’une classe dérive d’un générique, le paramètre de type doit déjà être défini avant que PowerShell analyse la classe dérivée. Si le paramètre de type du générique est une classe Ou une énumération PowerShell définie dans le même fichier ou bloc de code, PowerShell génère une erreur.

Pour dériver une classe d’une classe de base générique avec un type personnalisé comme paramètre de type, définissez la classe ou l’énumération du paramètre de type dans un autre fichier ou module et utilisez l’instruction using module pour charger la définition de type.

Pour obtenir un exemple montrant comment hériter d’une classe de base générique, consultez l’exemple 3.

Classes utiles pour hériter

Il existe quelques classes qui peuvent être utiles pour hériter lors de la création de modules PowerShell. Cette section répertorie quelques classes de base et ce qu’une classe dérivée d’elles peut être utilisée.

  • System.Attribute : dériver des classes pour définir des attributs qui peuvent être utilisés pour les variables, les paramètres, les définitions de classe et d’énumération, etc.
  • System.Management.Automation.ArgumentTransformationAttribute - Dérivez des classes pour gérer la conversion d’entrée pour une variable ou un paramètre en un type de données spécifique.
  • System.Management.Automation.ValidateArgumentsAttribute - Dérivez des classes pour appliquer une validation personnalisée aux variables, paramètres et propriétés de classe.
  • System.Collections.Generic.List - Dériver des classes pour faciliter la création et la gestion de listes d’un type de données spécifique.
  • System.Exception : dériver des classes pour définir des erreurs personnalisées.

Implémentation d’interfaces

Une classe PowerShell qui implémente une interface doit implémenter tous les membres de cette interface. L’omission des membres de l’interface d’implémentation provoque une erreur d’analyse dans le script.

Remarque

PowerShell ne prend pas en charge la déclaration de nouvelles interfaces dans le script PowerShell. Au lieu de cela, les interfaces doivent être déclarées dans le code .NET et ajoutées à la session avec l’applet Add-Type de commande ou l’instruction using assembly .

Lorsqu’une classe implémente une interface, elle peut être utilisée comme toute autre classe qui implémente cette interface. Certaines commandes et opérations limitent leurs types pris en charge aux classes qui implémentent une interface spécifique.

Pour passer en revue un exemple d’implémentation d’interfaces, consultez l’exemple 2.

Interfaces utiles à implémenter

Il existe quelques classes d’interface qui peuvent être utiles pour hériter lors de la création de modules PowerShell. Cette section répertorie quelques classes de base et ce qu’une classe dérivée d’elles peut être utilisée.

  • System.IEquatable : cette interface permet aux utilisateurs de comparer deux instances de la classe. Lorsqu’une classe n’implémente pas cette interface, PowerShell vérifie l’équivalence entre deux instances à l’aide de l’égalité de référence. En d’autres termes, une instance de la classe est égale uniquement à elle-même, même si les valeurs de propriété sur deux instances sont identiques.
  • System.IComparable : cette interface permet aux utilisateurs de comparer les instances de la classe avec les opérateurs de -gecomparaison , -ltet -gt les -leopérateurs de comparaison. Lorsqu’une classe n’implémente pas cette interface, ces opérateurs déclenchent une erreur.
  • System.IFormattable : cette interface permet aux utilisateurs de mettre en forme des instances de la classe en différentes chaînes. Cela est utile pour les classes qui ont plusieurs représentations sous forme de chaîne standard, telles que les éléments budgétaires, les bibliographies et les températures.
  • System.IConvertible : cette interface permet aux utilisateurs de convertir des instances de la classe en d’autres types d’exécution. Cela est utile pour les classes qui ont une valeur numérique sous-jacente ou peuvent être converties en une seule.

Limites

  • PowerShell ne prend pas en charge la définition d’interfaces dans le code de script.

    Solution de contournement : définissez des interfaces en C# et référencez l’assembly qui définit les interfaces.

  • Les classes PowerShell ne peuvent hériter qu’d’une seule classe de base.

    Solution de contournement : l’héritage de classe est transitif. Une classe dérivée peut hériter d’une autre classe dérivée pour obtenir les propriétés et méthodes d’une classe de base.

  • Lors de l’héritage d’une classe ou d’une interface générique, le paramètre de type du générique doit déjà être défini. Une classe ne peut pas se définir comme paramètre de type pour une classe ou une interface.

    Solution de contournement : Pour dériver d’une classe de base ou d’une interface générique, définissez le type personnalisé dans un autre .psm1 fichier et utilisez l’instruction using module pour charger le type. Il n’existe aucune solution de contournement pour un type personnalisé à utiliser lui-même comme paramètre de type lors de l’héritage d’un générique.

Voir aussi