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 Enheten, $dev är ett objekt eller en instans av typen Enhet. Varje instans av Enheten kan ha olika värden i sina egenskaper.

Stödda scenarier

  • 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 definitioner av anpassad typ.
  • 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>]}

Kommentar

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 någon 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 bokklass 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 fungerar. När du har skapat en instans av klassen Bok 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 Bok i exempel 2. Klassen BookList kan inte markeras som statisk, men implementeringen definierar endast 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 en runspace

Metoden ShowRunspaceId()[UnsafeClass] för rapporterar olika tråd-ID:er 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($_)
    }
}

Kommentar

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 fler 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 tabbavslut 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 med Get-Memberanvänder du parametern Force .
  • Visas inte i tabbslut 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.

Kommentar

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 egenskaperna och metoderna för basklassen. 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 hur du härleder klasser som ärver från en basklass eller implementerar gränssnitt finns i about_Classes_Inheritance.

Tillhörighet till runspace

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 samlas 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 runspace-tillhö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.

Kommentar

När 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 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 . Att läsa in modulen igen fungerar bara för ändringar av funktioner i rotmodulen. Import-Module läser inte in några kapslade moduler igen. Det finns inte heller något 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 utvecklingspraxis är att dela upp 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ösning för dessa begränsningar, om sådana finns.

Allmänna begränsningar

  • Klassmedlemmar kan inte använda PSReference som typ.

    Lösning: Inga.

  • PowerShell-klasser kan inte tas bort eller läsas in igen i en session.

    Lösning: Starta en ny session.

  • PowerShell-klasser som definieras 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 och static gäller endast för klassmedlemmar, inte en klassdefinition.

    Lösning: Inga.

  • PowerShell-klasser är inte säkra att använda i parallell körning över runspaces. När du anropar metoder i en klass samlar PowerShell tillbaka anropen till den Runspace där klassen skapades, vilket kan skada tillståndet för Runspace eller orsaka ett dödläge.

    Lösning: Inga.

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 konstruktorns brödtext med valideringsattributet.

  • Konstruktorparametrar kan inte definiera standardvärden. Parametrarna är alltid obligatoriska.

    Lösning: Inga.

  • Om någon överlagring av en konstruktor är dold behandlas även varje överlagring för konstruktorn som dold.

    Lösning: Inga.

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: Inga.

  • Om någon överlagring av en metod är dold behandlas även varje överlagring för den metoden som dold.

    Lösning: Inga.

Egenskapsbegränsningar

  • Statiska egenskaper är alltid föränderliga. PowerShell-klasser kan inte definiera oföränderliga statiska egenskaper.

    Lösning: Inga.

  • 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 egenskaperna och metoderna 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 allmänt basgränssnitt definierar du den anpassade typen i en annan .psm1 fil och använder instruktionen using 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 du ärver från en generisk.

Se även