À propos des classes
Description courte
Décrit comment utiliser des classes pour créer vos propres types personnalisés.
Description longue
À compter de la version 5.0, PowerShell a une syntaxe formelle pour définir des classes et d’autres types définis par l’utilisateur. L’ajout de classes permet aux développeurs et aux professionnels de l’informatique d’adopter PowerShell pour un plus large éventail de cas d’usage.
Une déclaration de classe est un blueprint utilisé pour créer des instances d’objets au moment de l’exécution. Lorsque vous définissez une classe, le nom de la classe est le nom du type. Par exemple, si vous déclarez une classe nommée Device et initialisez une variable $dev
sur une nouvelle instance de Device, $dev
est un objet ou une instance de type Device. Chaque instance de l’appareil peut avoir des valeurs différentes dans ses propriétés.
Scénarios pris en charge
- Définissez des types personnalisés dans PowerShell à l’aide d’une sémantique de programmation orientée objet, comme les classes, les propriétés, les méthodes, l’héritage, etc.
- Définissez les ressources DSC et leurs types associés à l’aide du langage PowerShell.
- Définissez des attributs personnalisés pour décorer des variables, des paramètres et des définitions de type personnalisées.
- Définissez des exceptions personnalisées qui peuvent être interceptées par leur nom de type.
Syntaxe
Définition de la syntaxe
Les définitions de classes utilisent la syntaxe suivante :
class <class-name> [: [<base-class>][,<interface-list>]] {
[[<attribute>] [hidden] [static] <property-definition> ...]
[<class-name>([<constructor-argument-list>])
{<constructor-statement-list>} ...]
[[<attribute>] [hidden] [static] <method-definition> ...]
}
Syntaxe d’instanciation
Pour instancier une instance d’une classe, utilisez l’une des syntaxes suivantes :
[$<variable-name> =] New-Object -TypeName <class-name> [
[-ArgumentList] <constructor-argument-list>]
[$<variable-name> =] [<class-name>]::new([<constructor-argument-list>])
[$<variable-name> =] [<class-name>]@{[<class-property-hashtable>]}
Remarque
Lorsque vous utilisez la [<class-name>]::new()
syntaxe, les crochets autour du nom de la classe sont obligatoires. Les crochets signalent une définition de type pour PowerShell.
La syntaxe de table de hachage fonctionne uniquement pour les classes qui ont un constructeur par défaut qui ne s’attend à aucun paramètre. Il crée une instance de la classe avec le constructeur par défaut, puis affecte les paires clé-valeur aux propriétés de l’instance. Si une clé dans la table de hachage n’est pas un nom de propriété valide, PowerShell génère une erreur.
Exemples
Exemple 1 - Définition minimale
Cet exemple montre la syntaxe minimale nécessaire pour créer une classe utilisable.
class Device {
[string]$Brand
}
$dev = [Device]::new()
$dev.Brand = "Fabrikam, Inc."
$dev
Brand
-----
Fabrikam, Inc.
Exemple 2 - Classe avec des membres d’instance
Cet exemple définit une classe Book avec plusieurs propriétés, constructeurs et méthodes. Chaque membre défini est un membre d’instance, et non un membre statique. Les propriétés et méthodes sont accessibles uniquement via une instance créée de la classe.
class Book {
# Class properties
[string] $Title
[string] $Author
[string] $Synopsis
[string] $Publisher
[datetime] $PublishDate
[int] $PageCount
[string[]] $Tags
# Default constructor
Book() { $this.Init(@{}) }
# Convenience constructor from hashtable
Book([hashtable]$Properties) { $this.Init($Properties) }
# Common constructor for title and author
Book([string]$Title, [string]$Author) {
$this.Init(@{Title = $Title; Author = $Author })
}
# Shared initializer method
[void] Init([hashtable]$Properties) {
foreach ($Property in $Properties.Keys) {
$this.$Property = $Properties.$Property
}
}
# Method to calculate reading time as 2 minutes per page
[timespan] GetReadingTime() {
if ($this.PageCount -le 0) {
throw 'Unable to determine reading time from page count.'
}
$Minutes = $this.PageCount * 2
return [timespan]::new(0, $Minutes, 0)
}
# Method to calculate how long ago a book was published
[timespan] GetPublishedAge() {
if (
$null -eq $this.PublishDate -or
$this.PublishDate -eq [datetime]::MinValue
) { throw 'PublishDate not defined' }
return (Get-Date) - $this.PublishDate
}
# Method to return a string representation of the book
[string] ToString() {
return "$($this.Title) by $($this.Author) ($($this.PublishDate.Year))"
}
}
L’extrait de code suivant crée une instance de la classe et montre comment elle se comporte. Après avoir créé une instance de la classe Book, l’exemple utilise les méthodes et GetPublishedAge()
les GetReadingTime()
méthodes pour écrire un message sur le livre.
$Book = [Book]::new(@{
Title = 'The Hobbit'
Author = 'J.R.R. Tolkien'
Publisher = 'George Allen & Unwin'
PublishDate = '1937-09-21'
PageCount = 310
Tags = @('Fantasy', 'Adventure')
})
$Book
$Time = $Book.GetReadingTime()
$Time = @($Time.Hours, 'hours and', $Time.Minutes, 'minutes') -join ' '
$Age = [Math]::Floor($Book.GetPublishedAge().TotalDays / 365.25)
"It takes $Time to read $Book,`nwhich was published $Age years ago."
Title : The Hobbit
Author : J.R.R. Tolkien
Synopsis :
Publisher : George Allen & Unwin
PublishDate : 9/21/1937 12:00:00 AM
PageCount : 310
Tags : {Fantasy, Adventure}
It takes 10 hours and 20 minutes to read The Hobbit by J.R.R. Tolkien (1937),
which was published 86 years ago.
Exemple 3 - Classe avec des membres statiques
La classe BookList de cet exemple s’appuie sur la classe Book dans l’exemple 2. Bien que la classe BookList ne puisse pas être marquée statique elle-même, l’implémentation définit uniquement la propriété statique Books et un ensemble de méthodes statiques pour la gestion de cette propriété.
class BookList {
# Static property to hold the list of books
static [System.Collections.Generic.List[Book]] $Books
# Static method to initialize the list of books. Called in the other
# static methods to avoid needing to explicit initialize the value.
static [void] Initialize() { [BookList]::Initialize($false) }
static [bool] Initialize([bool]$force) {
if ([BookList]::Books.Count -gt 0 -and -not $force) {
return $false
}
[BookList]::Books = [System.Collections.Generic.List[Book]]::new()
return $true
}
# Ensure a book is valid for the list.
static [void] Validate([book]$Book) {
$Prefix = @(
'Book validation failed: Book must be defined with the Title,'
'Author, and PublishDate properties, but'
) -join ' '
if ($null -eq $Book) { throw "$Prefix was null" }
if ([string]::IsNullOrEmpty($Book.Title)) {
throw "$Prefix Title wasn't defined"
}
if ([string]::IsNullOrEmpty($Book.Author)) {
throw "$Prefix Author wasn't defined"
}
if ([datetime]::MinValue -eq $Book.PublishDate) {
throw "$Prefix PublishDate wasn't defined"
}
}
# Static methods to manage the list of books.
# Add a book if it's not already in the list.
static [void] Add([Book]$Book) {
[BookList]::Initialize()
[BookList]::Validate($Book)
if ([BookList]::Books.Contains($Book)) {
throw "Book '$Book' already in list"
}
$FindPredicate = {
param([Book]$b)
$b.Title -eq $Book.Title -and
$b.Author -eq $Book.Author -and
$b.PublishDate -eq $Book.PublishDate
}.GetNewClosure()
if ([BookList]::Books.Find($FindPredicate)) {
throw "Book '$Book' already in list"
}
[BookList]::Books.Add($Book)
}
# Clear the list of books.
static [void] Clear() {
[BookList]::Initialize()
[BookList]::Books.Clear()
}
# Find a specific book using a filtering scriptblock.
static [Book] Find([scriptblock]$Predicate) {
[BookList]::Initialize()
return [BookList]::Books.Find($Predicate)
}
# Find every book matching the filtering scriptblock.
static [Book[]] FindAll([scriptblock]$Predicate) {
[BookList]::Initialize()
return [BookList]::Books.FindAll($Predicate)
}
# Remove a specific book.
static [void] Remove([Book]$Book) {
[BookList]::Initialize()
[BookList]::Books.Remove($Book)
}
# Remove a book by property value.
static [void] RemoveBy([string]$Property, [string]$Value) {
[BookList]::Initialize()
$Index = [BookList]::Books.FindIndex({
param($b)
$b.$Property -eq $Value
}.GetNewClosure())
if ($Index -ge 0) {
[BookList]::Books.RemoveAt($Index)
}
}
}
Maintenant que BookList est défini, le livre de l’exemple précédent peut être ajouté à la liste.
$null -eq [BookList]::Books
[BookList]::Add($Book)
[BookList]::Books
True
Title : The Hobbit
Author : J.R.R. Tolkien
Synopsis :
Publisher : George Allen & Unwin
PublishDate : 9/21/1937 12:00:00 AM
PageCount : 310
Tags : {Fantasy, Adventure}
L’extrait de code suivant appelle les méthodes statiques pour la classe.
[BookList]::Add([Book]::new(@{
Title = 'The Fellowship of the Ring'
Author = 'J.R.R. Tolkien'
Publisher = 'George Allen & Unwin'
PublishDate = '1954-07-29'
PageCount = 423
Tags = @('Fantasy', 'Adventure')
}))
[BookList]::Find({
param ($b)
$b.PublishDate -gt '1950-01-01'
}).Title
[BookList]::FindAll({
param($b)
$b.Author -match 'Tolkien'
}).Title
[BookList]::Remove($Book)
[BookList]::Books.Title
[BookList]::RemoveBy('Author', 'J.R.R. Tolkien')
"Titles: $([BookList]::Books.Title)"
[BookList]::Add($Book)
[BookList]::Add($Book)
The Fellowship of the Ring
The Hobbit
The Fellowship of the Ring
The Fellowship of the Ring
Titles:
Book 'The Hobbit by J.R.R. Tolkien (1937)' already in list
At C:\code\classes.examples.ps1:114 char:13
+ throw "Book '$Book' already in list"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Book 'The Hobbi...alread
y in list:String) [], RuntimeException
+ FullyQualifiedErrorId : Book 'The Hobbit by J.R.R. Tolkien (1937)'
already in list
Propriétés de classe
Les propriétés sont des variables déclarées dans l’étendue de classe. Une propriété peut être de n’importe quel type intégré ou d’une instance d’une autre classe. Les classes peuvent avoir zéro ou plusieurs propriétés. Les classes n’ont pas de nombre maximal de propriétés.
Pour plus d’informations, consultez about_Classes_Properties.
Méthodes de classe
Les méthodes définissent les actions qu’une classe peut effectuer. Les méthodes peuvent prendre des paramètres qui spécifient des données d’entrée. Les méthodes définissent toujours un type de sortie. Si une méthode ne retourne aucune sortie, elle doit avoir le type de sortie Void . Si une méthode ne définit pas explicitement un type de sortie, le type de sortie de la méthode est Void.
Pour plus d’informations, consultez about_Classes_Methods.
Constructeurs de classe
Les constructeurs vous permettent de définir des valeurs par défaut et de valider la logique d’objet au moment de la création de l’instance de la classe. Les constructeurs ont le même nom que la classe. Les constructeurs peuvent avoir des paramètres pour initialiser les membres de données du nouvel objet.
Pour plus d’informations, consultez about_Classes_Constructors.
Mot clé masqué
Le hidden
mot clé masque un membre de classe. Le membre est toujours accessible à l’utilisateur et est disponible dans toutes les étendues dans lesquelles l’objet est disponible.
Les membres masqués sont masqués dans l’applet Get-Member
de commande et ne peuvent pas être affichés à l’aide de la saisie semi-automatique de tabulation ou d’IntelliSense en dehors de la définition de classe.
Le hidden
mot clé s’applique uniquement aux membres de classe, et non à une classe elle-même.
Les membres de classe masqués sont les suivants :
- Non inclus dans la sortie par défaut de la classe.
- Non inclus dans la liste des membres de classe retournés par l’applet de
Get-Member
commande. Pour afficher les membres masqués avecGet-Member
, utilisez le paramètre Force . - Non affiché dans la saisie semi-automatique de tabulation ou IntelliSense, sauf si la saisie semi-automatique se produit dans la classe qui définit le membre masqué.
- Membres publics de la classe. Ils sont accessibles, hérités et modifiés. Le masquage d’un membre ne le rend pas privé. Il masque uniquement le membre comme décrit dans les points précédents.
Remarque
Lorsque vous masquez une surcharge pour une méthode, cette méthode est supprimée d’IntelliSense, des résultats d’achèvement et de la sortie par défaut pour Get-Member
.
Lorsque vous masquez un constructeur, l’option new()
est supprimée d’IntelliSense et des résultats de saisie semi-automatique.
Pour plus d’informations sur le mot clé, consultez about_Hidden. Pour plus d’informations sur les propriétés masquées, consultez about_Classes_Properties. Pour plus d’informations sur les méthodes masquées, consultez about_Classes_Methods. Pour plus d’informations sur les constructeurs masqués, consultez about_Classes_Constructors.
Mot clé statique
Le static
mot clé définit une propriété ou une méthode qui existe dans la classe et ne nécessite aucune instance.
Une propriété statique est toujours disponible, indépendamment de l’instanciation de classe. Une propriété statique est partagée entre toutes les instances de la classe. Une méthode statique est toujours disponible. Toutes les propriétés statiques sont actives pour l’ensemble de la session.
Le static
mot clé s’applique uniquement aux membres de classe, et non à une classe elle-même.
Pour plus d’informations sur les propriétés statiques, consultez about_Classes_Properties. Pour plus d’informations sur les méthodes statiques, consultez about_Classes_Methods. Pour plus d’informations sur les constructeurs statiques, consultez about_Classes_Constructors.
Héritage dans les classes PowerShell
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.
PowerShell ne prend pas en charge plusieurs héritages. Les classes ne peuvent pas hériter directement de plusieurs classes.
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 peut être utilisée comme n’importe quelle autre classe implémentant cette interface.
Pour plus d’informations sur la dérivation des classes qui héritent d’une classe de base ou implémentent des interfaces, consultez about_Classes_Inheritance.
Exportation de classes avec accélérateurs de type
Par défaut, les modules PowerShell n’exportent pas automatiquement les classes et les énumérations définies dans PowerShell. Les types personnalisés ne sont pas disponibles en dehors du module sans appeler une using module
instruction.
Toutefois, si un module ajoute des accélérateurs de type, ces accélérateurs de type sont immédiatement disponibles dans la session après l’importation du module par les utilisateurs.
Remarque
L’ajout d’accélérateurs de type à la session utilise une API interne (non publique). L’utilisation de cette API peut entraîner des conflits. Le modèle décrit ci-dessous génère une erreur si un accélérateur de type portant le même nom existe déjà lorsque vous importez le module. Il supprime également les accélérateurs de type lorsque vous supprimez le module de la session.
Ce modèle garantit que les types sont disponibles dans une session. Il n’affecte pas IntelliSense ou la saisie semi-automatique lors de la création d’un fichier de script dans VS Code.
Pour obtenir des suggestions IntelliSense et de saisie semi-automatique pour les types personnalisés dans VS Code, vous devez ajouter une using module
instruction en haut du script.
Le modèle suivant montre comment inscrire des classes et des énumérations PowerShell en tant qu’accélérateurs de type dans un module. Ajoutez l’extrait de code au module de script racine après toutes les définitions de type. Vérifiez que la $ExportableTypes
variable contient chacun des types que vous souhaitez rendre accessibles aux utilisateurs lorsqu’ils importent le module. L’autre code ne nécessite aucune modification.
# Define the types to export with type accelerators.
$ExportableTypes =@(
[DefinedTypeName]
)
# 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()
Lorsque les utilisateurs importent le module, tous les types ajoutés aux accélérateurs de type pour la session sont immédiatement disponibles pour IntelliSense et la saisie semi-automatique. Lorsque le module est supprimé, il s’agit donc des accélérateurs de type.
Importation manuelle de classes à partir d’un module PowerShell
Import-Module
et l’instruction #requires
importent uniquement les fonctions de module, les alias et les variables, comme défini par le module. Les classes ne sont pas importées.
Si un module définit des classes et des énumérations, mais n’ajoute pas d’accélérateurs de type pour ces types, utilisez une using module
instruction pour les importer.
L’instruction using module
importe des classes et des énumérations à partir du module racine (ModuleToProcess
) d’un module de script ou d’un module binaire. Elle n’importe pas de manière cohérente les classes définies dans les modules imbriqués ou les classes définies dans les scripts qui sont sources par points dans le module racine. Définissez les classes que vous souhaitez mettre à la disposition des utilisateurs en dehors du module directement dans le module racine.
Pour plus d’informations sur l’instruction using
, consultez about_Using.
Chargement du code nouvellement modifié pendant le développement
Pendant le développement d’un module de script, il est courant d’apporter des modifications au code, puis de charger la nouvelle version du module à l’aide Import-Module
du paramètre Force . Le rechargement du module fonctionne uniquement pour les modifications apportées aux fonctions dans le module racine. Import-Module
ne recharge aucun module imbriqué. En outre, il n’existe aucun moyen de charger des classes mises à jour.
Pour vous assurer que vous exécutez la dernière version, vous devez démarrer une nouvelle session.
Les classes et les énumérations définies dans PowerShell et importées avec une using
instruction ne peuvent pas être déchargées.
Une autre pratique de développement courante consiste à séparer votre code en différents fichiers. Si vous avez une fonction dans un fichier qui utilise des classes définies dans un autre module, vous devez utiliser l’instruction using module
pour vous assurer que les fonctions ont les définitions de classe nécessaires.
Le type PSReference n’est pas pris en charge avec les membres de classe
L’accélérateur [ref]
de type est abrégé pour la classe PSReference . L’utilisation [ref]
de type cast d’un membre de classe échoue en mode silencieux. Les API qui utilisent [ref]
des paramètres ne peuvent pas être utilisées avec des membres de classe. La classe PSReference a été conçue pour prendre en charge les objets COM. Les objets COM ont des cas où vous devez passer une valeur par référence.
Pour plus d’informations, consultez la classe PSReference.
Limites
Les listes suivantes incluent des limitations pour la définition de classes PowerShell et la solution de contournement pour ces limitations, le cas échéant.
Limitations générales
Les membres de classe ne peuvent pas utiliser PSReference comme type.
Solution de contournement : aucune.
Les classes PowerShell ne peuvent pas être déchargées ou rechargées dans une session.
Solution de contournement : démarrez une nouvelle session.
Les classes PowerShell définies dans un module ne sont pas importées automatiquement.
Solution de contournement : ajoutez les types définis à la liste des accélérateurs de type dans le module racine. Cela rend les types disponibles lors de l’importation de module.
Les
hidden
mots clés etstatic
les mots clés s’appliquent uniquement aux membres de classe, et non à une définition de classe.Solution de contournement : aucune.
Limitations du constructeur
Le chaînage du constructeur n’est pas implémenté.
Solution de contournement : définissez des méthodes masquées
Init()
et appelez-les à partir des constructeurs.Les paramètres du constructeur ne peuvent pas utiliser d’attributs, y compris les attributs de validation.
Solution de contournement : réaffectez les paramètres dans le corps du constructeur avec l’attribut de validation.
Les paramètres du constructeur ne peuvent pas définir de valeurs par défaut. Les paramètres sont toujours obligatoires.
Solution de contournement : aucune.
Si une surcharge d’un constructeur est masquée, chaque surcharge du constructeur est traitée comme masquée également.
Solution de contournement : aucune.
Limitations des méthodes
Les paramètres de méthode ne peuvent pas utiliser d’attributs, y compris les attributs de validation.
Solution de contournement : réaffectez les paramètres dans le corps de la méthode avec l’attribut de validation ou définissez la méthode dans le constructeur statique avec l’applet
Update-TypeData
de commande.Les paramètres de méthode ne peuvent pas définir de valeurs par défaut. Les paramètres sont toujours obligatoires.
Solution de contournement : définissez la méthode dans le constructeur statique avec l’applet de
Update-TypeData
commande.Les méthodes sont toujours publiques, même lorsqu’elles sont masquées. Elles peuvent être remplacées lorsque la classe est héritée.
Solution de contournement : aucune.
Si une surcharge d’une méthode est masquée, chaque surcharge de cette méthode est traitée comme masquée également.
Solution de contournement : aucune.
Limitations de propriété
Les propriétés statiques sont toujours mutables. Les classes PowerShell ne peuvent pas définir de propriétés statiques immuables.
Solution de contournement : aucune.
Les propriétés ne peuvent pas utiliser l’attribut ValidateScript , car les arguments d’attribut de propriété de classe doivent être des constantes.
Solution de contournement : Définissez une classe qui hérite du type ValidateArgumentsAttribute et utilisez cet attribut à la place.
Les propriétés déclarées directement ne peuvent pas définir d’implémentations getter et setter personnalisées.
Solution de contournement : définissez une propriété masquée et utilisez-la
Update-TypeData
pour définir la logique getter et setter visibles.Les propriétés ne peuvent pas utiliser l’attribut Alias . L’attribut s’applique uniquement aux paramètres, aux applets de commande et aux fonctions.
Solution de contournement : utilisez l’applet
Update-TypeData
de commande pour définir des alias dans les constructeurs de classe.Lorsqu’une classe PowerShell est convertie en JSON avec l’applet
ConvertTo-Json
de commande, le code JSON de sortie inclut toutes les propriétés masquées et leurs valeurs.Solution de contournement : aucune
Limitations de l’héritage
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’instructionusing 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.