about_Classes

Kort beskrivning

Beskriver hur du kan använda klasser för att skapa egna anpassade typer.

Lång beskrivning

PowerShell 5.0 lägger till en formell syntax för att definiera klasser och andra användardefinierade typer. Genom att lägga till klasser kan utvecklare och IT-experter använda PowerShell för ett bredare utbud av användningsfall. Det förenklar utvecklingen av PowerShell-artefakter och påskyndar täckningen av hanteringsytor.

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 Device och initierar en variabel till en ny instans av Enhet , är ett objekt eller $dev en instans av typen $dev Enhet. Varje instans av enhet kan ha olika värden i sina egenskaper.

Scenarier som stöds

  • Definiera anpassade typer i PowerShell med hjälp av välbekant objektorienterad programmeringssemantik som klasser, egenskaper, metoder, arv osv.
  • Felsök typer med hjälp av PowerShell-språket.
  • Generera och hantera undantag med hjälp av formella mekanismer.
  • Definiera DSC-resurser och deras associerade typer med hjälp av PowerShell-språket.

Syntax

Klasser deklareras med 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> ...]
}

Klasser instansieras med 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>])

Anteckning

När du använder [<class-name>]::new() syntaxen är hakparenteser runt klassnamnet obligatoriska. Hakparenteserna signalerar en typdefinition för PowerShell.

Exempelsyntax och användning

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 = "Microsoft"
$dev
Brand
-----
Microsoft

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 har ingen begränsning i antalet egenskaper som de har.

Exempelklass med enkla egenskaper

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku
}

$device = [Device]::new()
$device.Brand = "Microsoft"
$device.Model = "Surface Pro 4"
$device.VendorSku = "5072641000"

$device
Brand     Model         VendorSku
-----     -----         ---------
Microsoft Surface Pro 4 5072641000

Exempel på komplexa typer i klassegenskaper

Det här exemplet definierar en tom rackklass med hjälp av enhetsklassen. I exemplen nedan visas hur du lägger till enheter i racket och hur du börjar med ett förinstallerat rack.

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku
}

class Rack {
    [string]$Brand
    [string]$Model
    [string]$VendorSku
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new(8)

}

$rack = [Rack]::new()

$rack

Brand     :
Model     :
VendorSku :
AssetId   :
Devices   : {$null, $null, $null, $null...}


Klassmetoder

Metoder definierar de åtgärder som en klass kan utföra. Metoder kan ta parametrar som tillhandahåller indata. Metoder kan returnera utdata. Data som returneras av en metod kan vara valfri definierad datatyp.

När du definierar en metod för en klass refererar du till det aktuella klassobjektet med hjälp av $this den automatiska variabeln. På så sätt kan du komma åt egenskaper och andra metoder som definierats i den aktuella klassen.

Exempel på enkel klass med egenskaper och metoder

Utöka rackklassen för att lägga till och ta bort enheter till eller från den.

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    [string]ToString(){
        return ("{0}|{1}|{2}" -f $this.Brand, $this.Model, $this.VendorSku)
    }
}

class Rack {
    [int]$Slots = 8
    [string]$Brand
    [string]$Model
    [string]$VendorSku
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    [void] AddDevice([Device]$dev, [int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $dev
    }

    [void]RemoveDevice([int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $null
    }

    [int[]] GetAvailableSlots(){
        [int]$i = 0
        return @($this.Devices.foreach{ if($_ -eq $null){$i}; $i++})
    }
}

$rack = [Rack]::new()

$surface = [Device]::new()
$surface.Brand = "Microsoft"
$surface.Model = "Surface Pro 4"
$surface.VendorSku = "5072641000"

$rack.AddDevice($surface, 2)

$rack
$rack.GetAvailableSlots()

Slots     : 8
Brand     :
Model     :
VendorSku :
AssetId   :
Devices   : {$null, $null, Microsoft|Surface Pro 4|5072641000, $null...}

0
1
3
4
5
6
7

Utdata i klassmetoder

Metoder bör ha en definierad returtyp. Om en metod inte returnerar utdata ska utdatatypen vara [void] .

I klassmetoder skickas inga objekt till pipelinen förutom de som nämns i return -instruktionen. Det finns inga oavsiktliga utdata till pipelinen från koden.

Anteckning

Detta skiljer sig i grunden från hur PowerShell-funktioner hanterar utdata, där allt går till pipelinen.

Icke-avslutande fel som skrivs till felströmmen inifrån en klassmetod skickas inte vidare. Du måste använda throw för att visa ett avslutande fel. Med Write-* hjälp av cmdletarna kan du fortfarande skriva till PowerShells utdataströmmar inifrån en klassmetod. Detta bör dock undvikas så att metoden skickar objekt med endast return -instruktionen.

Metodutdata

Det här exemplet visar inga oavsiktliga utdata till pipelinen från klassmetoder, förutom i return -instruktionen.

class FunWithIntegers
{
    [int[]]$Integers = 0..10

    [int[]]GetOddIntegers(){
        return $this.Integers.Where({ ($_ % 2) })
    }

    [void] GetEvenIntegers(){
        # this following line doesn't go to the pipeline
        $this.Integers.Where({ ($_ % 2) -eq 0})
    }

    [string]SayHello(){
        # this following line doesn't go to the pipeline
        "Good Morning"

        # this line goes to the pipeline
        return "Hello World"
    }
}

$ints = [FunWithIntegers]::new()
$ints.GetOddIntegers()
$ints.GetEvenIntegers()
$ints.SayHello()
1
3
5
7
9
Hello World

Konstruktor

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 argument för att initiera datamedlemmarna i det nya objektet.

Klassen kan ha noll eller flera konstruktorer definierade. Om ingen konstruktor definieras får klassen en standardparameterlös konstruktor. Den här konstruktorn initierar alla medlemmar till sina standardvärden. Objekttyper och strängar får null-värden. När du definierar konstruktorn skapas ingen standardparameterlös konstruktor. Skapa en parameterlös konstruktor om det behövs.

Konstruktorn grundläggande syntax

I det här exemplet definieras enhetsklassen med egenskaper och en konstruktor. Om du vill använda den här klassen måste användaren ange värden för de parametrar som anges i konstruktorn.

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    Device(
        [string]$b,
        [string]$m,
        [string]$vsk
    ){
        $this.Brand = $b
        $this.Model = $m
        $this.VendorSku = $vsk
    }
}

[Device]$surface = [Device]::new("Microsoft", "Surface Pro 4", "5072641000")

$surface
Brand     Model         VendorSku
-----     -----         ---------
Microsoft Surface Pro 4 5072641000

Exempel med flera konstruktorer

I det här exemplet definieras enhetsklassen med egenskaper, en standardkonstruktor och en konstruktor för att initiera instansen.

Standardkonstruktorn ställer in varumärket på Undefined (Odefinierad) och lämnar modell- och leverantörs-SKU med null-värden.

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    Device(){
        $this.Brand = 'Undefined'
    }

    Device(
        [string]$b,
        [string]$m,
        [string]$vsk
    ){
        $this.Brand = $b
        $this.Model = $m
        $this.VendorSku = $vsk
    }
}

[Device]$somedevice = [Device]::new()
[Device]$surface = [Device]::new("Microsoft", "Surface Pro 4", "5072641000")

$somedevice
$surface
Brand       Model           VendorSku
-----       -----           ---------
Undefined
Microsoft   Surface Pro 4   5072641000

Dolt attribut

Attributet hidden döljer en egenskap eller metod. Egenskapen eller metoden ä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 och kan inte visas med tabbslut Get-Member eller IntelliSense utanför klassdefinitionen.

Mer information finns i About_hidden.

Exempel på användning av dolda attribut

När ett Rack-objekt skapas är antalet platser för enheter ett fast värde som inte ska ändras vid något tillfälle. Det här värdet är känt när det skapas.

Med hjälp av det dolda attributet kan utvecklaren hålla antalet platser dolda och förhindrar oavsiktliga ändringar av rackstorleken.

class Device {
    [string]$Brand
    [string]$Model
}

class Rack {
    [int] hidden $Slots = 8
    [string]$Brand
    [string]$Model
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack ([string]$b, [string]$m, [int]$capacity){
        ## argument validation here

        $this.Brand = $b
        $this.Model = $m
        $this.Slots = $capacity

        ## reset rack size to new capacity
        $this.Devices = [Device[]]::new($this.Slots)
    }
}

[Rack]$r1 = [Rack]::new("Microsoft", "Surface Pro 4", 16)

$r1
$r1.Devices.Length
$r1.Slots
Brand     Model         Devices
-----     -----         -------
Microsoft Surface Pro 4 {$null, $null, $null, $null...}
16
16

Observera att egenskapen Platser inte visas i $r1 utdata. Men storleken ändrades av konstruktorn.

Statiskt attribut

Attributet 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 instantiering. En statisk egenskap delas mellan alla instanser av klassen. En statisk metod är alltid tillgänglig. Alla statiska egenskaper är live för hela sessionsintervallet.

Exempel med statiska attribut och metoder

Anta att racken som instansieras här finns i ditt datacenter. Därför vill du hålla reda på racken i din kod.

class Device {
    [string]$Brand
    [string]$Model
}

class Rack {
    hidden [int] $Slots = 8
    static [Rack[]]$InstalledRacks = @()
    [string]$Brand
    [string]$Model
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack ([string]$b, [string]$m, [string]$id, [int]$capacity){
        ## argument validation here

        $this.Brand = $b
        $this.Model = $m
        $this.AssetId = $id
        $this.Slots = $capacity

        ## reset rack size to new capacity
        $this.Devices = [Device[]]::new($this.Slots)

        ## add rack to installed racks
        [Rack]::InstalledRacks += $this
    }

    static [void]PowerOffRacks(){
        foreach ($rack in [Rack]::InstalledRacks) {
            Write-Warning ("Turning off rack: " + ($rack.AssetId))
        }
    }
}

Testa statisk egenskap och metod finns

PS> [Rack]::InstalledRacks.Length
0

PS> [Rack]::PowerOffRacks()

PS> (1..10) | ForEach-Object {
>>   [Rack]::new("Adatum Corporation", "Standard-16",
>>     $_.ToString("Std0000"), 16)
>> } > $null

PS> [Rack]::InstalledRacks.Length
10

PS> [Rack]::InstalledRacks[3]
Brand              Model       AssetId Devices
-----              -----       ------- -------
Adatum Corporation Standard-16 Std0004 {$null, $null, $null, $null...}

PS> [Rack]::PowerOffRacks()
WARNING: Turning off rack: Std0001
WARNING: Turning off rack: Std0002
WARNING: Turning off rack: Std0003
WARNING: Turning off rack: Std0004
WARNING: Turning off rack: Std0005
WARNING: Turning off rack: Std0006
WARNING: Turning off rack: Std0007
WARNING: Turning off rack: Std0008
WARNING: Turning off rack: Std0009
WARNING: Turning off rack: Std0010

Observera att antalet rack ökar varje gång du kör det här exemplet.

Attribut för egenskapsvalidering

Med valideringsattribut kan du testa att värden som ges till egenskaper uppfyller definierade krav. Validering utlöses den tidpunkt då värdet tilldelas. Se about_functions_advanced_parameters.

Exempel med valideringsattribut

class Device {
    [ValidateNotNullOrEmpty()][string]$Brand
    [ValidateNotNullOrEmpty()][string]$Model
}

[Device]$dev = [Device]::new()

Write-Output "Testing dev"
$dev

$dev.Brand = ""
Testing dev

Brand Model
----- -----

Exception setting "Brand": "The argument is null or empty. Provide an
argument that is not null or empty, and then try the command again."
At C:\tmp\Untitled-5.ps1:11 char:1
+ $dev.Brand = ""
+ ~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting

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 för basklassen. Du kan lägga till eller åsidosätta metoder och egenskaper efter behov.

PowerShell stöder inte flera arv. Klasser kan inte ärva från mer än en klass. Du kan dock använda gränssnitt för detta ändamål.

Implementeringen av arv definieras av : operatorn , vilket innebär att utöka den här klassen eller implementera dessa gränssnitt. Den härledda klassen bör alltid vara längst till vänster i klassdeklarationen.

Exempel med enkel arvssyntax

I det här exemplet visas den enkla syntaxen för arv av PowerShell-klasser.

Class Derived : Base {...}

Det här exemplet visar arv med en gränssnittsdeklaration som kommer efter basklassen.

Class Derived : Base, Interface {...}

Exempel på enkelt arv i PowerShell-klasser

I det här exemplet är rack- och enhetsklasserna som används i föregående exempel bättre definierade för att: undvika egenskapsupprepningar, bättre justera gemensamma egenskaper och återanvända vanlig affärslogik.

De flesta objekt i datacentret är företagstillgångar, vilket är klokt att börja spåra dem som tillgångar. Enhetstyper definieras av DeviceType uppräkningen. Mer information om uppräkningar about_Enum finns i informationen om uppräkningar.

I vårt exempel definierar vi bara Rack och ComputeServer ; båda tilläggen till klassen Device .

enum DeviceType {
    Undefined = 0
    Compute = 1
    Storage = 2
    Networking = 4
    Communications = 8
    Power = 16
    Rack = 32
}

class Asset {
    [string]$Brand
    [string]$Model
}

class Device : Asset {
    hidden [DeviceType]$devtype = [DeviceType]::Undefined
    [string]$Status

    [DeviceType] GetDeviceType(){
        return $this.devtype
    }
}

class ComputeServer : Device {
    hidden [DeviceType]$devtype = [DeviceType]::Compute
    [string]$ProcessorIdentifier
    [string]$Hostname
}

class Rack : Device {
    hidden [DeviceType]$devtype = [DeviceType]::Rack
    hidden [int]$Slots = 8

    [string]$Datacenter
    [string]$Location
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack (){
        ## Just create the default rack with 8 slots
    }

    Rack ([int]$s){
        ## Add argument validation logic here
        $this.Devices = [Device[]]::new($s)
    }

    [void] AddDevice([Device]$dev, [int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $dev
    }

    [void] RemoveDevice([int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $null
    }
}

$FirstRack = [Rack]::new(16)
$FirstRack.Status = "Operational"
$FirstRack.Datacenter = "PNW"
$FirstRack.Location = "F03R02.J10"

(0..15).ForEach({
    $ComputeServer = [ComputeServer]::new()
    $ComputeServer.Brand = "Fabrikam, Inc."       ## Inherited from Asset
    $ComputeServer.Model = "Fbk5040"              ## Inherited from Asset
    $ComputeServer.Status = "Installed"           ## Inherited from Device
    $ComputeServer.ProcessorIdentifier = "x64"    ## ComputeServer
    $ComputeServer.Hostname = ("r1s" + $_.ToString("000")) ## ComputeServer
    $FirstRack.AddDevice($ComputeServer, $_)
  })

$FirstRack
$FirstRack.Devices
Datacenter : PNW
Location   : F03R02.J10
Devices    : {r1s000, r1s001, r1s002, r1s003...}
Status     : Operational
Brand      :
Model      :

ProcessorIdentifier : x64
Hostname            : r1s000
Status              : Installed
Brand               : Fabrikam, Inc.
Model               : Fbk5040

ProcessorIdentifier : x64
Hostname            : r1s001
Status              : Installed
Brand               : Fabrikam, Inc.
Model               : Fbk5040

<... content truncated here for brevity ...>

ProcessorIdentifier : x64
Hostname            : r1s015
Status              : Installed
Brand               : Fabrikam, Inc.
Model               : Fbk5040

Anropa basklasskonstruktorer

Om du vill anropa en basklasskonstruktor från en underklass lägger du till base nyckelordet .

class Person {
    [int]$Age

    Person([int]$a)
    {
        $this.Age = $a
    }
}

class Child : Person
{
    [string]$School

    Child([int]$a, [string]$s ) : base($a) {
        $this.School = $s
    }
}

[Child]$littleone = [Child]::new(10, "Silver Fir Elementary School")

$littleone.Age

10

Anropa basklassmetoder

Om du vill åsidosätta befintliga metoder i underklasser deklarerar du metoder med samma namn och signatur.

class BaseClass
{
    [int]days() {return 1}
}
class ChildClass1 : BaseClass
{
    [int]days () {return 2}
}

[ChildClass1]::new().days()

2

Om du vill anropa basklassmetoder från åsidosatta implementeringar, typge till basklassen ([baseclass]$this) vid anrop.

class BaseClass
{
    [int]days() {return 1}
}
class ChildClass1 : BaseClass
{
    [int]days () {return 2}
    [int]basedays() {return ([BaseClass]$this).days()}
}

[ChildClass1]::new().days()
[ChildClass1]::new().basedays()

2
1

Ärv från gränssnitt

PowerShell-klasser kan implementera ett gränssnitt med samma arvssyntax som används för att utöka basklasser. Eftersom gränssnitt tillåter flera arv kan en PowerShell-klass som implementerar ett gränssnitt ärva från flera typer genom att avgränsa typnamnen efter kolonet ( ) med : kommatecken ( , ). En PowerShell-klass som implementerar ett gränssnitt måste implementera alla medlemmar i gränssnittet. Om du utelämnar implementeringsgränssnittsmedlemmarna uppstår ett parse-time-fel i skriptet.

Anteckning

PowerShell stöder för närvarande inte deklarera nya gränssnitt i PowerShell-skript.

class MyComparable : System.IComparable
{
    [int] CompareTo([object] $obj)
    {
        return 0;
    }
}

class MyComparableBar : bar, System.IComparable
{
    [int] CompareTo([object] $obj)
    {
        return 0;
    }
}

Importera klasser från en PowerShell-modul

Import-Module och #requires -instruktionen importerar endast modulfunktioner, alias och variabler enligt definitionen i modulen. Klasser importeras inte. Instruktionen using module importerar de klasser som definierats i modulen. Om modulen inte läses in i den aktuella sessionen misslyckas using -instruktionen. Mer information om using -instruktionen finns i about_Using.

Instruktionen using module importerar klasser från rotmodulen ( ModuleToProcess ) för en skriptmodul eller binär modul. Den importerar inte konsekvent klasser som definierats i kapslade moduler eller klasser som definierats i skript som är dot-sourced i modulen. Klasser som du vill ska vara tillgängliga för användare utanför modulen ska definieras i rotmodulen.

Läsa 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 med parametern Force. Detta fungerar endast för ändringar av funktioner i rotmodulen. Import-Module läser inte in kapslade moduler på nytt. Det finns heller inget sätt att läsa in uppdaterade klasser.

För att säkerställa att du kör den senaste versionen måste du ta bort modulen med hjälp av Remove-Module cmdleten . Remove-Module tar bort rotmodulen, alla kapslade moduler och alla klasser som definierats i modulerna. Sedan kan du läsa in modulen och klasserna på nytt med Import-Module hjälp av - och using module -instruktionen.

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 för att säkerställa att funktionerna har de using module klassdefinitioner som behövs.

PSReference-typen stöds inte med klassmedlemmar

Användning av [ref] typ-cast med en klassmedlem misslyckas tyst. API:er som [ref] använder 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 ett värde i som referens.

Mer information om typen [ref] finns i PSReference-klass.

Se även