about_Classes
Kort beskrivning
Beskriver hur du kan använda klasser för att skapa egna anpassade typer.
Lång beskrivning
Från och med version 5.0 har PowerShell en formell syntax för att definiera klasser och andra användardefinierade typer. Genom att lägga till klasser kan utvecklare och IT-proffs använda PowerShell för ett bredare utbud av användningsfall.
En klassdeklaration är en skiss som används för att skapa instanser av objekt vid körning. När du definierar en klass är klassnamnet namnet på typen. Om du till exempel deklarerar en klass med namnet Enhet och initierar en variabel $dev
till en ny instans av Enhet, $dev
är ett objekt eller en instans av typen Enhet. Varje instans av Enheten kan ha olika värden i sina egenskaper.
Scenarier som stöds
- Definiera anpassade typer i PowerShell med hjälp av objektorienterad programmeringssemantik som klasser, egenskaper, metoder, arv osv.
- Definiera DSC-resurser och deras associerade typer med hjälp av PowerShell-språket.
- Definiera anpassade attribut för att dekorera variabler, parametrar och anpassade typdefinitioner.
- Definiera anpassade undantag som kan fångas av deras typnamn.
Syntax
Definitionssyntax
Klassdefinitioner använder följande syntax:
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> ...]
}
Instansieringssyntax
Om du vill instansiera en instans av en klass använder du någon av följande syntaxer:
[$<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>]}
Anteckning
När du använder syntaxen [<class-name>]::new()
är hakparenteser runt klassnamnet obligatoriska. Hakparenteserna signalerar en typdefinition för PowerShell.
Hashtabellsyntaxen fungerar bara för klasser som har en standardkonstruktor som inte förväntar sig några parametrar. Den skapar en instans av klassen med standardkonstruktorn och tilldelar sedan nyckel/värde-paren till instansegenskaperna. Om en nyckel i hashtabellen inte är ett giltigt egenskapsnamn genererar PowerShell ett fel.
Exempel
Exempel 1 – Minimal definition
Det här exemplet visar den minsta syntax som krävs för att skapa en användbar klass.
class Device {
[string]$Brand
}
$dev = [Device]::new()
$dev.Brand = "Fabrikam, Inc."
$dev
Brand
-----
Fabrikam, Inc.
Exempel 2 – Klass med instansmedlemmar
Det här exemplet definierar en Book-klass med flera egenskaper, konstruktorer och metoder. Varje definierad medlem är en instansmedlem , inte en statisk medlem. Egenskaper och metoder kan bara nås via en skapad instans av klassen.
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))"
}
}
Följande kodfragment skapar en instans av klassen och visar hur den beter sig. När du har skapat en instans av klassen Book använder GetReadingTime()
exemplet metoderna och GetPublishedAge()
för att skriva ett meddelande om boken.
$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.
Exempel 3 – Klass med statiska medlemmar
Klassen BookList i det här exemplet bygger på klassen Book i exempel 2. Klassen BookList kan inte markeras som statisk, men implementeringen definierar bara den statiska egenskapen Böcker och en uppsättning statiska metoder för att hantera den egenskapen.
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)
}
}
}
Nu när BookList har definierats kan boken från föregående exempel läggas till i listan.
$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}
Följande kodfragment anropar de statiska metoderna för klassen.
[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:
Exception:
Line |
84 | throw "Book '$Book' already in list"
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Book 'The Hobbit by J.R.R. Tolkien (1937)' already in list
Exempel 4 – Parallell körning som skadar ett körningsutrymme
Metoden ShowRunspaceId()
för [UnsafeClass]
rapporterar olika tråd-ID:t men samma runspace-ID. Så småningom är sessionstillståndet skadat och orsakar ett fel, till exempel Global scope cannot be removed
.
# Class definition with Runspace affinity (default behavior)
class UnsafeClass {
static [object] ShowRunspaceId($val) {
return [PSCustomObject]@{
ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId
RunspaceId = [runspace]::DefaultRunspace.Id
}
}
}
$unsafe = [UnsafeClass]::new()
while ($true) {
1..10 | ForEach-Object -Parallel {
Start-Sleep -ms 100
($using:unsafe)::ShowRunspaceId($_)
}
}
Anteckning
Det här exemplet körs i en oändlig loop. Ange Ctrl+C för att stoppa körningen.
Klassegenskaper
Egenskaper är variabler som deklareras i klassomfånget. En egenskap kan vara av valfri inbyggd typ eller en instans av en annan klass. Klasser kan ha noll eller flera egenskaper. Klasser har inte maximalt antal egenskaper.
Mer information finns i about_Classes_Properties.
Klassmetoder
Metoder definierar de åtgärder som en klass kan utföra. Metoder kan ta parametrar som anger indata. Metoder definierar alltid en utdatatyp. Om en metod inte returnerar några utdata måste den ha utdatatypen Void . Om en metod inte uttryckligen definierar en utdatatyp är metodens utdatatyp Void.
Mer information finns i about_Classes_Methods.
Klasskonstruktorer
Med konstruktorer kan du ange standardvärden och validera objektlogik när du skapar -instansen av klassen. Konstruktorer har samma namn som klassen . Konstruktorer kan ha parametrar för att initiera datamedlemmarna i det nya objektet.
Mer information finns i about_Classes_Constructors.
Dolt nyckelord
Nyckelordet hidden
döljer en klassmedlem. Medlemmen är fortfarande tillgänglig för användaren och är tillgänglig i alla omfång där objektet är tillgängligt.
Dolda medlemmar är dolda från cmdleten Get-Member
och kan inte visas med tabbifyllning eller IntelliSense utanför klassdefinitionen.
Nyckelordet hidden
gäller endast för klassmedlemmar, inte för själva klassen.
Dolda klassmedlemmar är:
- Ingår inte i standardutdata för klassen.
- Ingår inte i listan över klassmedlemmar som returneras av cmdleten
Get-Member
. Om du vill visa dolda medlemmar medGet-Member
använder du parametern Force . - Visas inte i tabbifyllning eller IntelliSense om inte slutförandet sker i klassen som definierar den dolda medlemmen.
- Offentliga medlemmar i klassen. De kan nås, ärvas och ändras. Att dölja en medlem gör det inte privat. Den döljer bara medlemmen enligt beskrivningen i föregående punkter.
Anteckning
När du döljer eventuell överlagring för en metod tas den metoden bort från IntelliSense, slutföranderesultat och standardutdata för Get-Member
.
När du döljer en konstruktor new()
tas alternativet bort från IntelliSense och slutföranderesultat.
Mer information om nyckelordet finns i about_Hidden. Mer information om dolda egenskaper finns i about_Classes_Properties. Mer information om dolda metoder finns i about_Classes_Methods. Mer information om dolda konstruktorer finns i about_Classes_Constructors.
Statiskt nyckelord
Nyckelordet static
definierar en egenskap eller en metod som finns i klassen och som inte behöver någon instans.
En statisk egenskap är alltid tillgänglig, oberoende av klass-instansiering. En statisk egenskap delas mellan alla instanser av klassen. En statisk metod är alltid tillgänglig. Alla statiska egenskaper är aktiva under hela sessionsintervallet.
Nyckelordet static
gäller endast för klassmedlemmar, inte för själva klassen.
Mer information om statiska egenskaper finns i about_Classes_Properties. Mer information om statiska metoder finns i about_Classes_Methods. Mer information om statiska konstruktorer finns i about_Classes_Constructors.
Arv i PowerShell-klasser
Du kan utöka en klass genom att skapa en ny klass som härleds från en befintlig klass. Den härledda klassen ärver basklassens egenskaper och metoder. Du kan lägga till eller åsidosätta basklassmedlemmarna efter behov.
PowerShell stöder inte flera arv. Klasser kan inte ärva direkt från mer än en klass.
Klasser kan också ärva från gränssnitt som definierar ett kontrakt. En klass som ärver från ett gränssnitt måste implementera det kontraktet. När den gör det kan klassen användas som vilken annan klass som helst som implementerar gränssnittet.
Mer information om att härleda klasser som ärver från en basklass eller implementera gränssnitt finns i about_Classes_Inheritance.
Runspace-tillhörighet
En runspace är driftmiljön för de kommandon som anropas av PowerShell. Den här miljön innehåller de kommandon och data som för närvarande finns och eventuella språkbegränsningar som för närvarande gäller.
En PowerShell-klass är kopplad till den Runspace där den skapas. Det är inte säkert att använda en PowerShell-klass i ForEach-Object -Parallel
.
Metodanrop i klassen kommer tillbaka till runspace där den skapades, vilket kan skada tillståndet för Runspace eller orsaka ett dödläge.
En bild av hur körningsrymdstillhörighet kan orsaka fel finns i Exempel 4.
Exportera klasser med typacceleratorer
Som standard exporterar PowerShell-moduler inte automatiskt klasser och uppräkningar som definierats i PowerShell. De anpassade typerna är inte tillgängliga utanför modulen utan att anropa en using module
instruktion.
Men om en modul lägger till typacceleratorer är dessa typacceleratorer omedelbart tillgängliga i sessionen efter att användarna har importerat modulen.
Anteckning
Om du lägger till typacceleratorer i sessionen används ett internt (inte offentligt) API. Om du använder det här API:et kan det orsaka konflikter. Mönstret som beskrivs nedan genererar ett fel om det redan finns en typaccelerator med samma namn när du importerar modulen. Den tar också bort typacceleratorerna när du tar bort modulen från sessionen.
Det här mönstret säkerställer att typerna är tillgängliga i en session. Det påverkar inte IntelliSense eller slutförande när du redigerar en skriptfil i VS Code.
För att få IntelliSense- och slutförandeförslag för anpassade typer i VS Code måste du lägga till en using module
instruktion överst i skriptet.
Följande mönster visar hur du kan registrera PowerShell-klasser och uppräkningar som typacceleratorer i en modul. Lägg till kodfragmentet i rotskriptmodulen efter alla typdefinitioner. Kontrollera att variabeln $ExportableTypes
innehåller var och en av de typer som du vill göra tillgängliga för användare när de importerar modulen. Den andra koden kräver ingen redigering.
# 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()
När användarna importerar modulen är alla typer som läggs till i typacceleratorerna för sessionen omedelbart tillgängliga för IntelliSense och slutförande. När modulen tas bort, så är även typacceleratorerna.
Importera klasser manuellt från en PowerShell-modul
Import-Module
och -instruktionen #requires
importerar endast modulfunktioner, alias och variabler enligt modulens definition. Klasser importeras inte.
Om en modul definierar klasser och uppräkningar men inte lägger till typacceleratorer för dessa typer använder du en using module
-instruktion för att importera dem.
Instruktionen using module
importerar klasser och uppräkningar från rotmodulen (ModuleToProcess
) i en skriptmodul eller binär modul. Den importerar inte konsekvent klasser som definierats i kapslade moduler eller klasser som definierats i skript som är punktbaserade i rotmodulen. Definiera klasser som du vill ska vara tillgängliga för användare utanför modulen direkt i rotmodulen.
Mer information om instruktionen finns i using
about_Using.
Läser in nyligen ändrad kod under utveckling
Under utvecklingen av en skriptmodul är det vanligt att göra ändringar i koden och sedan läsa in den nya versionen av modulen med hjälp Import-Module
av parametern Force . Omläsning av modulen fungerar bara för ändringar av funktioner i rotmodulen. Import-Module
läser inte in några kapslade moduler igen. Det finns heller inget sätt att läsa in några uppdaterade klasser.
För att säkerställa att du kör den senaste versionen måste du starta en ny session.
Klasser och uppräkningar som definierats i PowerShell och importerats med en using
instruktion kan inte tas bort.
En annan vanlig metod för utveckling är att separera koden i olika filer. Om du har en funktion i en fil som använder klasser som definierats i en annan modul bör du använda -instruktionen using module
för att säkerställa att funktionerna har de klassdefinitioner som behövs.
PSReference-typen stöds inte med klassmedlemmar
Typacceleratorn [ref]
är en förkortning för KLASSEN PSReference . Det [ref]
går inte att använda för att skriva en klassmedlem tyst. API:er som använder [ref]
parametrar kan inte användas med klassmedlemmar. PSReference-klassen har utformats för att stödja COM-objekt. COM-objekt har fall där du behöver skicka in ett värde med referens.
Mer information finns i PSReference-klass.
Begränsningar
Följande listor innehåller begränsningar för att definiera PowerShell-klasser och lösningar för dessa begränsningar, om sådana finns.
Allmänna begränsningar
Klassmedlemmar kan inte använda PSReference som typ.
Lösning: Ingen.
PowerShell-klasser kan inte tas bort eller läsas in på nytt i en session.
Lösning: Starta en ny session.
PowerShell-klasser som definierats i en modul importeras inte automatiskt.
Lösning: Lägg till de definierade typerna i listan över typacceleratorer i rotmodulen. Detta gör typerna tillgängliga vid modulimport.
Nyckelorden
hidden
ochstatic
gäller endast för klassmedlemmar, inte en klassdefinition.Lösning: Ingen.
PowerShell-klasser är inte säkra att använda vid parallell körning över runspaces. När du anropar metoder i en klass samlar PowerShell tillbaka anropen till Runspace där klassen skapades, vilket kan skada tillståndet för Runspace eller orsaka ett dödläge.
Lösning: Ingen.
Konstruktorbegränsningar
Konstruktorlänkning implementeras inte.
Lösning: Definiera dolda
Init()
metoder och anropa dem inifrån konstruktorerna.Konstruktorparametrar kan inte använda några attribut, inklusive valideringsattribut.
Lösning: Tilldela om parametrarna i konstruktortexten med valideringsattributet.
Konstruktorparametrar kan inte definiera standardvärden. Parametrarna är alltid obligatoriska.
Lösning: Ingen.
Om någon överlagring av en konstruktor är dold behandlas även varje överlagring för konstruktorn som dold.
Lösning: Ingen.
Metodbegränsningar
Metodparametrar kan inte använda några attribut, inklusive valideringsattribut.
Lösning: Tilldela om parametrarna i metodtexten med valideringsattributet eller definiera metoden i den statiska konstruktorn med cmdleten
Update-TypeData
.Metodparametrar kan inte definiera standardvärden. Parametrarna är alltid obligatoriska.
Lösning: Definiera metoden i den statiska konstruktorn med cmdleten
Update-TypeData
.Metoder är alltid offentliga, även när de är dolda. De kan åsidosättas när klassen ärvs.
Lösning: Ingen.
Om någon överlagring av en metod är dold behandlas även varje överlagring för den metoden som dold.
Lösning: Ingen.
Egenskapsbegränsningar
Statiska egenskaper är alltid föränderliga. PowerShell-klasser kan inte definiera oföränderliga statiska egenskaper.
Lösning: Ingen.
Egenskaper kan inte använda attributet ValidateScript eftersom attributargument för klassegenskap måste vara konstanter.
Lösning: Definiera en klass som ärver från typen ValidateArgumentsAttribute och använd det attributet i stället.
Direkt deklarerade egenskaper kan inte definiera anpassade implementeringar av getter och setter.
Lösning: Definiera en dold egenskap och använd
Update-TypeData
för att definiera den synliga getter- och setterlogik.Egenskaper kan inte använda aliasattributet. Attributet gäller endast för parametrar, cmdletar och funktioner.
Lösning: Använd cmdleten
Update-TypeData
för att definiera alias i klasskonstruktorerna.När en PowerShell-klass konverteras till JSON med cmdleten
ConvertTo-Json
innehåller utdata-JSON alla dolda egenskaper och deras värden.Lösning: Inga
Arvsbegränsningar
PowerShell stöder inte definition av gränssnitt i skriptkod.
Lösning: Definiera gränssnitt i C# och referera till sammansättningen som definierar gränssnitten.
PowerShell-klasser kan bara ärva från en basklass.
Lösning: Klassarv är transitivt. En härledd klass kan ärva från en annan härledd klass för att hämta egenskaper och metoder för en basklass.
När du ärver från en allmän klass eller ett gränssnitt måste typparametern för det generiska redan ha definierats. En klass kan inte definiera sig själv som typparameter för en klass eller ett gränssnitt.
Lösning: Om du vill härleda från en allmän basklass eller ett gränssnitt definierar du den anpassade typen i en annan
.psm1
fil och använder instruktionenusing module
för att läsa in typen. Det finns ingen lösning för en anpassad typ att använda sig själv som typparameter när den ärver från en allmän.