簡短描述
描述如何使用類別來建立自己的自定義類型。
完整描述
從 5.0 版開始,PowerShell 具有定義類別和其他使用者定義型別的正式語法。 新增類別可讓開發人員和IT專業人員接受PowerShell,以取得更廣泛的使用案例。
類別宣告是用來在運行時間建立物件實例的藍圖。 當您定義類別時,類別名稱是型別的名稱。 例如,如果您宣告名為 Device 的類別,並將變數初始化為 Device$dev 裝置的每個實例在其屬性中都可以有不同的值。
支援的案例
- 在 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>])
[$<variable-name> =] [<class-name>]<convertable-value-type>
注意
使用 [<class-name>]::new() 語法時,類別名稱周圍的括弧是必要的。 括號表示 PowerShell 的類型定義。
這個 <convertable-value-type> 語法只適用於預設建構子且不期望任何參數的類別。 它會用預設建構子建立該類別的實例,然後使用執行時型別轉換來指派所提供的值。
範例
範例 1 - 最小定義
此範例示範建立可用類別所需的最小語法。
class Device {
[string]$Brand
}
$dev = [Device]::new()
$dev.Brand = "Fabrikam, Inc."
$dev
Brand
-----
Fabrikam, Inc.
範例 2 - 使用實例化語法
此範例定義了一個具有多個性質的 Book 類別,但沒有建構子。
class Book {
# Class properties
[string] $Title
[string] $Author
[string] $Synopsis
[string] $Publisher
[datetime] $PublishDate
[int] $PageCount
[string[]] $Tags
}
以下範例展示了預設建構子如何利用型別強制從相容值指派值。 在此範例中,使用雜湊表來提供屬性值。
$Book1 = [Book] @{
Title = '1984'
Author = 'George Orwell'
Synopsis = ''
Publisher = 'Secker & Warburg'
PublishDate = '1949-06-08'
PageCount = 328
Tags = @('Dystopian', 'Political Fiction', 'Social Science Fiction')
}
$Book1
Title : 1984
Author : George Orwell
Synopsis :
Publisher : Secker & Warburg
PublishDate : 6/8/1949 12:00:00 AM
PageCount : 328
Tags : {Dystopian, Political Fiction, Social Science Fiction}
雜湊表的鍵值對會被分配給實例屬性。 如果雜湊表中的任何鍵不是有效的屬性名稱,實例化就會失敗。
在此範例中,使用陣列來提供通用列表的值。
$List = [System.Collections.Generic.List[int]] @(42, 43)
$List
42
43
範例 3 - 有實例成員的類別
此範例會使用數個 屬性、建構函式和方法來定義 Book 類別。 每個定義的成員都是 實例 成員,而不是靜態成員。 屬性和方法只能透過 類別的建立實例來存取。
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))"
}
}
下列代碼段會建立 類別的實例,並示範其運作方式。 建立 Book 類別的訊息。
$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.
範例 4 - 具有靜態成員的類別
此範例中的 BookList 類別是建立在前一個範例中的 Book 類別之上。 雖然 BookList 類別本身無法標記為靜態,但實作中只定義了 Books 靜態屬性及一組用於管理該屬性的靜態方法。
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)
}
}
}
現在已定義 BookList,就可以將上一個範例中的書籍新增至清單。
$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}
下列代碼段會呼叫 類別的靜態方法。
[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
範例 5 - 有與無 Runspace 親和力的類別定義
的 ShowRunspaceId()[UnsafeClass] 方法會報告不同的線程標識符,但相同的 Runspace 識別碼。 最後,工作階段狀態已損毀,導致錯誤,例如 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($_)
}
}
注意
這個範例會在無限迴圈中執行。 輸入 Ctrl+C 以停止執行。
報告ShowRunspaceId()[SafeClass]不同線程和 Runspace 識別符的方法。
# Class definition with NoRunspaceAffinity attribute
[NoRunspaceAffinity()]
class SafeClass {
static [Object] ShowRunspaceId($Val) {
return [pscustomobject]@{
ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId
RunspaceId = [runspace]::DefaultRunspace.Id
}
}
}
$safe = [SafeClass]::new()
while ($true) {
1..10 | ForEach-Object -Parallel {
Start-Sleep -ms 100
($Using:safe)::ShowRunspaceId($_)
}
}
注意
這個範例會在無限迴圈中執行。 輸入 Ctrl+C 以停止執行。
類別屬性
屬性是在類別範圍中宣告的變數。 屬性可以是任何內建類型或另一個類別的實例。 類別可以有零個或多個屬性。 類別沒有屬性計數上限。
如需詳細資訊,請參閱 about_Classes_Properties。
類別方法
方法會定義類別可以執行的動作。 方法可以接受指定輸入數據的參數。 方法一律會定義輸出類型。 如果方法未傳回任何輸出,它必須具有 Void 輸出類型。 如果方法未明確定義輸出類型,則方法的輸出類型為 Void。
如需詳細資訊,請參閱 about_Classes_Methods。
類別建構函式
建構函式可讓您設定預設值,並在建立 類別的實例時驗證對象邏輯。 建構函式的名稱與類別相同。 建構函式可能會有參數,以初始化新對象的數據成員。
如需詳細資訊,請參閱 about_Classes_Constructors。
Hidden 關鍵詞
關鍵詞 hidden 會隱藏類別成員。 該成員仍可供使用者存取,而且可在物件可供使用的所有範圍內使用。
隱藏的成員會隱藏在 Cmdlet 中 Get-Member ,且無法在類別定義外部使用 Tab 鍵自動完成或 IntelliSense 來顯示。
關鍵詞 hidden 只適用於類別成員,而不是類別本身。
隱藏的類別成員包括:
- 不包含在 類別的預設輸出中。
- Cmdlet 所
Get-Member傳回的類別成員清單中未包含。 若要使用Get-Member顯示隱藏的成員,請使用 Force 參數。 - 除非完成發生在定義隱藏成員的類別中,否則不會顯示在索引標籤完成或 IntelliSense 中。
- 類別的公用成員。 您可以存取、繼承和修改它們。 隱藏成員不會讓它成為私用。 它只會隱藏上一個點中所述的成員。
注意
當您隱藏方法的任何多載時,該方法會從 IntelliSense 移除、完成結果,以及的預設 Get-Member輸出。
當您隱藏任何建構函式時,選項 new() 會從 IntelliSense 和完成結果中移除。
如需 關鍵詞的詳細資訊,請參閱 about_Hidden。 如需隱藏屬性的詳細資訊,請參閱 about_Classes_Properties。 如需隱藏方法的詳細資訊,請參閱 about_Classes_Methods。 如需隱藏建構函式的詳細資訊,請參閱 about_Classes_Constructors。
靜態關鍵字
static關鍵詞會定義存在於 類別中的屬性或方法,而且不需要實例。
靜態屬性一律可供使用,與類別具現化無關。 靜態屬性會在 類別的所有實例之間共用。 靜態方法一律可供使用。 整個工作階段範圍的所有靜態屬性都是即時的。
關鍵詞 static 只適用於類別成員,而不是類別本身。
如需靜態屬性的詳細資訊,請參閱 about_Classes_Properties。 如需靜態方法的詳細資訊,請參閱 about_Classes_Methods。 如需靜態建構函式的詳細資訊,請參閱 about_Classes_Constructors。
PowerShell 類別中的繼承
您可以建立衍生自現有類別的新類別來擴充類別。 衍生類別會繼承基類的屬性和方法。 您可以視需要新增或覆寫基類成員。
PowerShell 不支援多重繼承。 類別無法直接繼承自一個以上的類別。
類別也可以繼承自定義合約的介面。 繼承自介面的類別必須實作該合約。 執行此作業時,可以使用 類別,就像實作該介面的任何其他類別一樣。
如需衍生繼承自基類或實作介面之類別的詳細資訊,請參閱 about_Classes_Inheritance。
NoRunspaceAffinity 屬性
Runspace 是 PowerShell 所叫用命令的作業環境。 此環境包含目前存在的命令和數據,以及目前套用的任何語言限制。
根據預設,PowerShell 類別會與 建立它的 Runspace 相關聯。 在中使用 ForEach-Object -Parallel PowerShell類別並不安全。
類別上的方法調用會封送回建立它的 Runspace,這可能會損毀 Runspace 的狀態或造成死結。
將 NoRunspaceAffinity 屬性新增至類別定義可確保PowerShell類別與特定 Runspace 無關。 實例和靜態的方法調用都會使用 執行中線程的 Runspace 和線程目前的會話狀態。
已在 PowerShell 7.4 中新增 屬性。
關於有該屬性與不具備該 NoRunspaceAffinity 屬性的類別行為差異的說明,請參見 範例 5。
匯出具有類型快速鍵的類別
根據預設,PowerShell 模組不會自動匯出 PowerShell 中定義的類別和列舉。 自定義類型無法在模組外部使用,而不需要呼叫 using module 語句。
不過,如果模組新增類型加速器,這些類型加速器會在使用者匯入模組之後立即在會話中使用。
注意
將類型加速器新增至會話會使用內部(非公用)API。 使用此 API 可能會導致衝突。 如果您匯入模組時已有相同名稱的類型加速器存在,以下所述的模式會擲回錯誤。 當您從會話中移除模組時,它也會移除類型加速器。
此模式可確保類型可在會話中使用。 在 VS Code 中撰寫腳本檔案時,不會影響 IntelliSense 或完成。
若要在 VS Code 中取得自定義類型的 IntelliSense 和完成建議,您必須將 語句新增 using module 至腳本頂端。
下列模式示範如何在模組中將PowerShell類別和列舉註冊為類型加速器。 將代碼段新增至任何類型定義之後的根腳本模組。 請確定 $ExportableTypes 變數包含您要在匯入模組時提供給使用者使用的每個類型。 其他程序代碼不需要任何編輯。
# 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()
當使用者匯入模組時,任何新增至會話類型加速器的類型都會立即可供 IntelliSense 和完成使用。 拿掉模組時,類型加速器也是如此。
從 PowerShell 模組手動匯入類別
Import-Module
#Requires和語句只會匯入模組函式、別名和變數,如模組所定義。 類別不會匯入。
如果模組定義類別和列舉,但不會為這些類型新增型別加速器,請使用 using module 語句來匯入它們。
語句 using module 會從文本模組或二進位模組的根模組 (ModuleToProcess) 匯入類別和列舉。 它不會一致地匯入巢狀模組中定義的類別,或定義在根模組中以點來源撰寫的腳本中所定義的類別。 定義您想要直接在根模組中供模組外部使用者使用的類別。
如需 語句的詳細資訊 using ,請參閱 about_Using。
在開發期間載入新變更的程式碼
在開發文本模組期間,通常會變更程式碼,然後使用 Force Import-Module 參數載入新版的模組。 重載模組只適用於根模組中函式的變更。
Import-Module 不會重載任何巢狀模組。 此外,無法載入任何更新的類別。
若要確保您執行的是最新版本,您必須啟動新的工作階段。
在 PowerShell 中定義的類別和列舉,且無法卸除使用 using 語句匯入的類別和列舉。
另一個常見的開發做法是將您的程式代碼分成不同的檔案。 如果您在某個檔案中使用另一個模組中定義的類別,您應該使用 using module 語句來確保函式具有所需的類別定義。
類別成員不支援 PSReference 類型
類型[ref]加速器是 PSReference 類別的速記。 使用 [ref] 進行類型轉換時,類別成員會以無訊息方式失敗。 使用 [ref] 參數的 API 無法與類別成員搭配使用。
PSReference 類別是設計來支援 COM 物件。 COM 物件有需要以傳址方式傳入值的情況。
如需詳細資訊,請參閱 PSReference 類別。
局限性
下列清單包含定義 PowerShell 類別的限制,以及如果有的話,這些限制的因應措施。
一般限制
類別成員無法使用 PSReference 作為其類型。
因應措施:無。
無法在會話中卸除或重載 PowerShell 類別。
因應措施:啟動新的工作階段。
模組中定義的 PowerShell 類別不會自動匯入。
因應措施:將定義的類型新增至根模組中的類型加速器清單。 這可在模組匯入時提供類型。
hidden和static關鍵詞僅適用於類別成員,而非類別定義。因應措施:無。
根據預設,PowerShell 類別在 Runspace 之間平行執行並不安全。 當您在類別上叫用方法時,PowerShell 會將調用封送回建立類別的 Runspace,這可能會損毀 Runspace 的狀態或造成死結。
因應措施:將
NoRunspaceAffinity屬性新增至類別宣告。
建構函式限制
未實作建構函式鏈結。
因應措施:定義隱藏
Init()的方法,並從建構函式內呼叫它們。建構函式參數無法使用任何屬性,包括驗證屬性。
因應措施:使用驗證屬性重新指派建構函式主體中的參數。
建構函式參數無法定義預設值。 參數一律為必要參數。
因應措施:無。
如果隱藏任何建構函式的多載,建構函式的每個多載也會被視為隱藏。
因應措施:無。
方法限制
方法參數無法使用任何屬性,包括驗證屬性。
因應措施:使用驗證屬性重新指派方法主體中的參數,或使用 Cmdlet 在靜態建構
Update-TypeData函式中定義 方法。方法參數無法定義預設值。 參數一律為必要參數。
因應措施:使用
Update-TypeDataCmdlet 在靜態建構函式中定義 方法。方法一律為公用,即使它們已隱藏也一樣。 繼承類別時可以覆寫它們。
因應措施:無。
如果隱藏方法的任何多載,該方法的每個多載也會被視為隱藏。
因應措施:無。
屬性限制
靜態屬性一律為可變動。 PowerShell 類別無法定義不可變的靜態屬性。
因應措施:無。
屬性無法使用 ValidateScript 屬性,因為類別屬性屬性自變數必須是常數。
因應措施:定義繼承自 ValidateArgumentsAttribute 類型的類別,並改用該屬性。
直接宣告的屬性無法定義自定義 getter 和 setter 實作。
因應措施:定義隱藏的屬性,並使用
Update-TypeData來定義可見的 getter 和 setter 邏輯。屬性無法使用 Alias 屬性。 屬性僅適用於參數、Cmdlet 和函式。
因應
Update-TypeData措施:使用 Cmdlet 在類別建構函式中定義別名。當 PowerShell 類別使用
ConvertTo-JsonCmdlet 轉換成 JSON 時,輸出 JSON 會包含所有隱藏的屬性及其值。因應措施:無
繼承限制
PowerShell 不支援在腳本程式代碼中定義介面。
因應措施:在 C# 中定義介面,並參考定義介面的元件。
PowerShell 類別只能繼承自一個基類。
因應措施:類別繼承是可轉移的。 衍生類別可以繼承自另一個衍生類別,以取得基類的屬性和方法。
從泛型類別或介面繼承時,必須已經定義泛型的類型參數。 類別無法將本身定義為類別或介面的類型參數。
因應措施:若要衍生自泛型基類或介面,請在不同的
.psm1檔案中定義自定義類型,並使用using module語句載入類型。 自定義類型在繼承自泛型時,不會有任何因應措施,以做為型別參數。