about_Classes

簡短描述

描述如何使用類別來建立您自己的自訂類型。

完整描述

PowerShell 5.0 新增正式語法,以定義類別和其他使用者定義型別。 新增類別可讓開發人員和 IT 專業人員採用 PowerShell,以取得更廣泛的使用案例。 它可簡化 PowerShell 成品的開發,並加速管理介面的涵蓋範圍。

類別宣告是用來在執行時間建立物件實例的藍圖。 當您定義類別時,類別名稱是型別的名稱。 例如,如果您宣告名為 Device 的類別,並將變數 $dev 初始化為 Device的新實例, $dev 則為 Device類型的物件或實例。 裝置的每個實例在其屬性中可以有不同的值。

支援的案例

  • 使用熟悉的物件導向程式設計語意,在 PowerShell 中定義自訂類型,例如類別、屬性、方法、繼承等。
  • 使用 PowerShell 語言對類型進行偵錯。
  • 使用正式機制產生和處理例外狀況。
  • 使用 PowerShell 語言定義 DSC 資源及其相關聯的類型。

語法

類別是使用下列語法來宣告:

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> ...]
}

類別是使用下列其中一種語法具現化:

[$<variable-name> =] New-Object -TypeName <class-name> [
  [-ArgumentList] <constructor-argument-list>]
[$<variable-name> =] [<class-name>]::new([<constructor-argument-list>])

注意

使用 [<class-name>]::new() 語法時,類別名稱周圍的括弧是必要的。 方括弧表示 PowerShell 的類型定義。

範例語法和使用方式

此範例顯示建立可用類別所需的最小語法。

class Device {
    [string]$Brand
}

$dev = [Device]::new()
$dev.Brand = "Fabrikam, Inc."
$dev
Brand
-----
Fabrikam, Inc.

類別屬性

屬性是在類別範圍宣告的變數。 屬性可以是任何內建類型或另一個類別的實例。 類別在屬性數目上沒有限制。

具有簡單屬性的範例類別

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

$device = [Device]::new()
$device.Brand = "Fabrikam, Inc."
$device.Model = "Fbk5040"
$device.VendorSku = "5072641000"

$device
Brand          Model   VendorSku
-----          -----   ---------
Fabrikam, Inc. Fbk5040 5072641000

類別屬性中的複雜類型範例

此範例會使用Device類別定義空的 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...}


類別方法

方法會定義類別可以執行的動作。 方法可能會採用提供輸入資料的參數。 方法可以傳回輸出。 方法傳回的資料可以是任何已定義的資料類型。

定義類別的方法時,您可以使用自動變數來參考目前的類別物件 $this 。 這可讓您存取目前類別中定義的屬性和其他方法。

具有屬性和方法的簡單類別範例

擴充 Rack 類別,以新增和移除裝置到其中或從中移除裝置。

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

$device = [Device]::new()
$device.Brand = "Fabrikam, Inc."
$device.Model = "Fbk5040"
$device.VendorSku = "5072641000"

$rack.AddDevice($device, 2)

$rack
$rack.GetAvailableSlots()

Slots     : 8
Devices   : {$null, $null, Fabrikam, Inc.|Fbk5040|5072641000, $null…}
Brand     :
Model     :
VendorSku :
AssetId   :

0
1
3
4
5
6
7

類別方法中的輸出

方法應該已定義傳回型別。 如果方法未傳回輸出,則輸出類型應該是 [void]

在類別方法中,除了 語句中所述 return 的物件以外,不會將物件傳送至管線。 程式碼不會意外輸出至管線。

注意

這基本上與 PowerShell 函式處理輸出的方式不同,所有專案都會移至管線。

從類別方法內部寫入錯誤資料流程的非終止錯誤不會通過。 您必須使用 throw 來呈現終止錯誤。 Write-*使用 Cmdlet,您仍然可以從類別方法內寫入 PowerShell 的輸出資料流程。 不過,應該避免這麼做,讓方法只 return 使用 語句發出物件。

方法輸出

這個範例會示範不會意外從類別方法輸出管線,但 語句除外 return

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

建構函式

建構函式可讓您設定預設值,並在建立 類別的實例時驗證物件邏輯。 建構函式的名稱與 類別相同。 建構函式可能有引數,以初始化新物件的資料成員。

類別可以定義零個或多個建構函式。 如果未定義建構函式,類別就會獲得預設無參數建構函式。 這個建構函式會將所有成員初始化為其預設值。 物件類型和字串會指定 Null 值。 當您定義建構函式時,不會建立預設無參數建構函式。 如有需要,請建立無參數建構函式。

建構函式基本語法

在此範例中,Device 類別是使用屬性和建構函式來定義。 若要使用此類別,使用者必須提供建構函式中所列參數的值。

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]$device = [Device]::new(
    "Fabrikam, Inc.",
    "Fbk5040",
    "5072641000"
)

$device
Brand          Model   VendorSku
-----          -----   ---------
Fabrikam, Inc. Fbk5040 5072641000

具有多個建構函式的範例

在此範例中, Device 類別是使用屬性、預設建構函式和初始化實例的建構函式來定義。

預設建構函式會將 品牌 設定為 Undefined,並將 modelvendor-sku 保留為 Null 值。

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]$server = [Device]::new(
    "Fabrikam, Inc.",
    "Fbk5040",
    "5072641000"
)

$someDevice, $server
Brand          Model   VendorSku
-----          -----   ---------
Undefined
Fabrikam, Inc. Fbk5040 5072641000

Hidden 關鍵字

關鍵字 hidden 會隱藏屬性或方法。 使用者仍然可以存取 屬性或方法,而且可在物件可供使用的所有範圍內使用。 隱藏成員會從 Get-Member Cmdlet 隱藏,而且無法在類別定義外部使用索引標籤完成或 IntelliSense 來顯示。

如需詳細資訊,請參閱 about_Hidden

使用隱藏關鍵字的範例

建立 Rack 物件時,裝置的插槽數目是固定值,隨時不應該變更。 此值在建立時是已知的。

使用 hidden 關鍵字可讓開發人員隱藏位置數目,並防止不小心變更機架的大小。

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("Fabrikam, Inc.", "Fbk5040", 16)

$r1
$r1.Devices.Length
$r1.Slots
Devices                       Brand          Model
-------                       -----          -----
{$null, $null, $null, $null…} Fabrikam, Inc. Fbk5040
16
16

請注意 ,輸出 中不會顯示 $r1 Slots 屬性。 不過,建構函式已變更大小。

Static 關鍵字

關鍵字 static 會定義存在於 類別中的屬性或方法,而且不需要實例。

靜態屬性一律可供使用,與類別具現化無關。 靜態屬性會跨 類別的所有實例共用。 靜態方法一律可供使用。 整個會話範圍的所有靜態屬性都是即時的。

使用靜態屬性和方法的範例

假設此處具現化的機架存在於資料中心,而您想要追蹤程式碼中的機架。

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

測試靜態屬性和方法存在

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

請注意,每次執行此範例時,機架數目都會增加。

使用屬性屬性

PowerShell 包含數個屬性類別,可用來增強資料類型資訊,並驗證指派給屬性的資料。 驗證屬性可讓您測試提供給屬性的值是否符合定義的需求。 驗證會在指派值時觸發。

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 isn't 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

如需可用屬性的詳細資訊,請參閱 about_Functions_Advanced_Parameters

PowerShell 類別中的繼承

您可以建立衍生自現有類別的新類別來擴充類別。 衍生類別會繼承基類的屬性。 您可以視需要新增或覆寫方法和屬性。

PowerShell 不支援多重繼承。 類別無法繼承自多個類別。 不過,您可以針對該用途使用介面。

繼承實作是使用 : 語法來定義,以擴充類別或實作介面。 衍生類別應該一律在類別宣告中最左邊。

此範例顯示基本的 PowerShell 類別繼承語法。

Class Derived : Base {...}

此範例顯示基類之後的介面宣告繼承。

Class Derived : Base, Interface {...}

PowerShell 類別中的繼承範例

在此範例中,先前範例中使用的 RackDevice 類別會更妥善地定義為:避免屬性重複、更妥善地對齊通用屬性,以及重複使用常見的商務邏輯。

資料中心的大部分物件都是公司資產,這很適合開始將其追蹤為資產。 列舉 DeviceType 會定義 類別所使用的裝置類型。 如需列舉的詳細資訊,請參閱 about_Enum

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

在我們的範例中,我們會將 和 ComputeServer 定義為 Rack 類別的 Device 延伸模組。

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

呼叫基類建構函式

若要從子類別叫用基類建構函式,請新增 base 關鍵字。

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

叫用基類方法

若要覆寫子類別中的現有方法,請使用相同名稱和簽章宣告方法。

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

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

2

若要從覆寫實作呼叫基類方法,請在叫用時轉換成基類 ([baseclass]$this) 。

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

繼承自介面

PowerShell 類別可以使用用來擴充基類的相同繼承語法來實作介面。 因為介面允許多重繼承,所以實作介面的 PowerShell 類別可能會繼承自多個類型,方法是在冒號 : (後面分隔類型名稱,) 逗號 (,) 。 實作介面的 PowerShell 類別必須實作該介面的所有成員。 省略實作介面成員會導致腳本中的剖析時間錯誤。

注意

PowerShell 不支援在 PowerShell 腳本中宣告新的介面。

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

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

從 PowerShell 模組匯入類別

Import-Module#requires和 語句只會匯入模組函式、別名和變數,如模組所定義。 類別不會匯入。 語句會 using module 匯入模組中定義的類別。 如果模組未在目前的會話中載入,語句就會 using 失敗。 如需 語句的詳細資訊 using ,請參閱 about_Using

語句會 using module 從腳本模組或二進位模組的根模組 (ModuleToProcess) 匯入類別。 它不會一致地匯入巢狀模組中定義的類別,或定義在模組中以點來源撰寫的腳本中定義的類別。 您想要供模組外部使用者使用的類別應該定義在根模組中。

在開發期間載入新變更的程式碼

在腳本模組的開發期間,通常會變更程式碼,然後使用Force參數載入新版本的模組 Import-Module 。 這僅適用于根模組中的函式變更。 Import-Module 不會重載任何巢狀模組。 此外,也無法載入任何更新的類別。

若要確保您執行的是最新版本,您必須使用 Remove-Module Cmdlet 卸載模組。 Remove-Module 會移除根模組、所有巢狀模組,以及模組中定義的任何類別。 然後,您可以使用 和 using module 語句來重載模組和類別 Import-Module

另一個常見的開發做法是將您的程式碼分成不同的檔案。 如果您在一個檔案中使用另一個模組中定義的類別,您應該使用 using module 語句來確保函式具有所需的類別定義。

類別成員不支援 PSReference 類型

PSReference類別的類型 [ref] 加速器是簡短的。 使用 [ref] 類型轉換類別成員會以無訊息方式失敗。 使用 [ref] 參數的 API 無法與類別成員搭配使用。 PSReference類別是設計來支援 COM 物件。 COM 物件有需要以傳址方式傳入值的情況。

如需詳細資訊,請參閱 PSReference 類別

另請參閱