about_Classes
簡短描述
描述如何使用類別來建立自己的自定義類型。
詳細描述
從 5.0 版開始,PowerShell 具有定義類別和其他使用者定義型別的正式語法。 新增類別可讓開發人員和IT專業人員接受PowerShell,以取得更廣泛的使用案例。
類別宣告是用來在運行時間建立物件實例的藍圖。 當您定義類別時,類別名稱是型別的名稱。 例如,如果您宣告名為 Device 的類別,並將變數$dev
初始化為 Device 的新實例,$dev
則為 Device 類型的物件或實例。 裝置的每個實例在其屬性中都可以有不同的值。
支援的案例
- 在 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>]@{[<class-property-hashtable>]}
注意
使用 [<class-name>]::new()
語法時,類別名稱周圍的括弧是必要的。 括號表示 PowerShell 的類型定義。
哈希表語法僅適用於具有預設建構函式且不需要任何參數的類別。 它會使用預設建構函式建立 類別的實例,然後將索引鍵/值組指派給實例屬性。 如果哈希表中的任何索引鍵不是有效的屬性名稱,PowerShell 就會引發錯誤。
範例
範例 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
# 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 類別的實例之後,此範例會使用 GetReadingTime()
和 GetPublishedAge()
方法來撰寫有關書籍的訊息。
$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.
範例 3 - 具有靜態成員的類別
此範例中的 BookList 類別是以範例 2 中的 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
範例 4 - 具有和不含 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
關鍵詞會定義存在於 類別中的屬性或方法,而且不需要實例。
靜態屬性一律可供使用,與類別具現化無關。 靜態屬性會在 類別的所有實例之間共用。 靜態方法一律可供使用。 整個工作階段範圍的所有靜態屬性都是即時的。
關鍵詞 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
屬性之類別行為差異的圖例,請參閱 範例 4。
匯出具有類型加速器的類別
根據預設,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-TypeData
Cmdlet 在靜態建構函式中定義 方法。方法一律為公用,即使它們已隱藏也一樣。 繼承類別時可以覆寫它們。
因應措施:無。
如果隱藏方法的任何多載,該方法的每個多載也會被視為隱藏。
因應措施:無。
屬性限制
靜態屬性一律為可變動。 PowerShell 類別無法定義不可變的靜態屬性。
因應措施:無。
屬性無法使用 ValidateScript 屬性,因為類別屬性屬性自變數必須是常數。
因應措施:定義繼承自 ValidateArgumentsAttribute 類型的類別,並改用該屬性。
直接宣告的屬性無法定義自定義 getter 和 setter 實作。
因應措施:定義隱藏的屬性,並使用
Update-TypeData
來定義可見的 getter 和 setter 邏輯。屬性無法使用 Alias 屬性。 屬性僅適用於參數、Cmdlet 和函式。
因應
Update-TypeData
措施:使用 Cmdlet 在類別建構函式中定義別名。當 PowerShell 類別使用
ConvertTo-Json
Cmdlet 轉換成 JSON 時,輸出 JSON 會包含所有隱藏的屬性及其值。因應措施:無
繼承限制
PowerShell 不支援在腳本程式代碼中定義介面。
因應措施:在 C# 中定義介面,並參考定義介面的元件。
PowerShell 類別只能繼承自一個基類。
因應措施:類別繼承是可轉移的。 衍生類別可以繼承自另一個衍生類別,以取得基類的屬性和方法。
從泛型類別或介面繼承時,必須已經定義泛型的類型參數。 類別無法將本身定義為類別或介面的類型參數。
因應措施:若要衍生自泛型基類或介面,請在不同的
.psm1
檔案中定義自定義類型,並使用using module
語句載入類型。 自定義類型在繼承自泛型時,不會有任何因應措施,以做為型別參數。