about_Classes_Methods
簡短描述
描述如何定義PowerShell類別的方法。
詳細描述
方法會定義類別可以執行的動作。 方法可以接受指定輸入數據的參數。 方法一律會定義輸出類型。 如果方法未傳回任何輸出,它必須具有 Void 輸出類型。 如果方法未明確定義輸出類型,則方法的輸出類型為 Void。
在類別方法中,除了語句中指定的 return
物件以外,不會將對象傳送至管線。 程式代碼中沒有意外輸出至管線。
注意
這與 PowerShell 函式處理輸出的方式基本不同,所有項目都會移至管線。
從類別方法內部寫入錯誤數據流的非決定性錯誤不會通過。 您必須使用 throw
來呈現終止錯誤。
Write-*
使用 Cmdlet,您仍然可以從類別方法內寫入 PowerShell 的輸出數據流。 Cmdlet 會遵守 呼叫範圍中的喜好設定變數 。 不過,您應該避免使用 Write-*
Cmdlet,讓 方法只使用 return
語句輸出物件。
類別方法可以使用自動變數來存取目前類別中定義的屬性和其他方法,來參考類別物件的 $this
目前實例。 靜態 $this
方法中無法使用自動變數。
語法
類別方法會使用下列語法:
單行語法
[[<attribute>]...] [hidden] [static] [<output-type>] <method-name> ([<method-parameters>]) { <body> }
多行語法
[[<attribute>]...]
[hidden]
[static]
[<output-type>] <method-name> ([<method-parameters>]) {
<body>
}
範例
範例 1 - 最小方法定義
GetVolume()
ExampleCube1 類別的 方法會傳回 Cube 的磁碟區。 它會將輸出類型定義為浮點數,並傳回相乘 實例的 Height、 Length 和 Width 屬性的結果。
class ExampleCube1 {
[float] $Height
[float] $Length
[float] $Width
[float] GetVolume() { return $this.Height * $this.Length * $this.Width }
}
$box = [ExampleCube1]@{
Height = 2
Length = 2
Width = 3
}
$box.GetVolume()
12
範例 2 - 具有參數的方法
方法 GeWeight()
會採用立方體密度的浮點數輸入,並傳回 Cube 的加權,計算為磁碟區乘以密度。
class ExampleCube2 {
[float] $Height
[float] $Length
[float] $Width
[float] GetVolume() { return $this.Height * $this.Length * $this.Width }
[float] GetWeight([float]$Density) {
return $this.GetVolume() * $Density
}
}
$cube = [ExampleCube2]@{
Height = 2
Length = 2
Width = 3
}
$cube.GetWeight(2.5)
30
範例 3 - 沒有輸出的方法
此範例會將 Validate()
輸出類型定義為 System.Void 的方法。 這個方法不會傳回任何輸出。 相反地,如果驗證失敗,則會擲回錯誤。 方法會在 GetVolume()
計算 Cube 的磁碟區之前呼叫 Validate()
。 如果驗證失敗,方法會在計算之前終止。
class ExampleCube3 {
[float] $Height
[float] $Length
[float] $Width
[float] GetVolume() {
$this.Validate()
return $this.Height * $this.Length * $this.Width
}
[void] Validate() {
$InvalidProperties = @()
foreach ($Property in @('Height', 'Length', 'Width')) {
if ($this.$Property -le 0) {
$InvalidProperties += $Property
}
}
if ($InvalidProperties.Count -gt 0) {
$Message = @(
'Invalid cube properties'
"('$($InvalidProperties -join "', '")'):"
"Cube dimensions must all be positive numbers."
) -join ' '
throw $Message
}
}
}
$Cube = [ExampleCube3]@{ Length = 1 ; Width = -1 }
$Cube
$Cube.GetVolume()
Height Length Width
------ ------ -----
0.00 1.00 -1.00
Exception:
Line |
20 | throw $Message
| ~~~~~~~~~~~~~~
| Invalid cube properties ('Height', 'Width'): Cube dimensions must
| all be positive numbers.
方法會擲回例外狀況,因為 Height 和 Width 屬性無效,導致 類別無法計算目前的磁碟區。
範例 4 - 具有多載的靜態方法
ExampleCube4 類別會定義具有兩個多載的靜態方法GetVolume()
。 第一個多載具有 Cube 維度的參數,以及指出方法是否應該驗證輸入的旗標。
第二個多載只包含數值輸入。 它會使用 呼叫第一個多載 $Static
作為 $true
。 第二個多載可讓使用者呼叫 方法,而不需要一律定義是否要嚴格驗證輸入。
類別也會定義為 GetVolume()
實例 (非靜態) 方法。 這個方法會呼叫第二個靜態多載,確保實例 GetVolume()
方法在傳回輸出值之前一律會驗證 Cube 的維度。
class ExampleCube4 {
[float] $Height
[float] $Length
[float] $Width
static [float] GetVolume(
[float]$Height,
[float]$Length,
[float]$Width,
[boolean]$Strict
) {
$Signature = "[ExampleCube4]::GetVolume({0}, {1}, {2}, {3})"
$Signature = $Signature -f $Height, $Length, $Width, $Strict
Write-Verbose "Called $Signature"
if ($Strict) {
[ValidateScript({$_ -gt 0 })]$Height = $Height
[ValidateScript({$_ -gt 0 })]$Length = $Length
[ValidateScript({$_ -gt 0 })]$Width = $Width
}
return $Height * $Length * $Width
}
static [float] GetVolume([float]$Height, [float]$Length, [float]$Width) {
$Signature = "[ExampleCube4]::GetVolume($Height, $Length, $Width)"
Write-Verbose "Called $Signature"
return [ExampleCube4]::GetVolume($Height, $Length, $Width, $true)
}
[float] GetVolume() {
Write-Verbose "Called `$this.GetVolume()"
return [ExampleCube4]::GetVolume(
$this.Height,
$this.Length,
$this.Width
)
}
}
$VerbosePreference = 'Continue'
$Cube = [ExampleCube4]@{ Height = 2 ; Length = 2 }
$Cube.GetVolume()
VERBOSE: Called $this.GetVolume()
VERBOSE: Called [ExampleCube4]::GetVolume(2, 2, 0)
VERBOSE: Called [ExampleCube4]::GetVolume(2, 2, 0, True)
MetadataError:
Line |
19 | [ValidateScript({$_ -gt 0 })]$Width = $Width
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| The variable cannot be validated because the value 0 is not a valid
| value for the Width variable.
方法定義中的詳細資訊訊息會示範如何呼叫靜態方法的初始呼叫 $this.GetVolume()
。
使用 Strict 參數直接呼叫靜態方法,做為 $false
磁碟區的傳回 0
。
[ExampleCube4]::GetVolume($Cube.Height, $Cube.Length, $Cube.Width, $false)
VERBOSE: Called [ExampleCube4]::GetVolume(2, 2, 0, False)
0
方法簽章和多載
每個類別方法都有定義如何呼叫 方法的唯一簽章。 方法的輸出類型、名稱和參數會定義方法簽章。
當類別定義多個具有相同名稱的方法時,該方法的定義會是 多載。 方法的多載必須有不同的參數。 方法無法定義兩個具有相同參數的實作,即使輸出類型不同也一樣。
下列類別會定義兩種方法和 Shuffle()
Deal()
。 方法 Deal()
會定義兩個多載,一個沒有任何參數,另一個則使用 Count 參數。
class CardDeck {
[string[]]$Cards = @()
hidden [string[]]$Dealt = @()
hidden [string[]]$Suits = @('Clubs', 'Diamonds', 'Hearts', 'Spades')
hidden [string[]]$Values = 2..10 + @('Jack', 'Queen', 'King', 'Ace')
CardDeck() {
foreach($Suit in $this.Suits) {
foreach($Value in $this.Values) {
$this.Cards += "$Value of $Suit"
}
}
$this.Shuffle()
}
[void] Shuffle() {
$this.Cards = $this.Cards + $this.Dealt | Where-Object -FilterScript {
-not [string]::IsNullOrEmpty($_)
} | Get-Random -Count $this.Cards.Count
}
[string] Deal() {
if ($this.Cards.Count -eq 0) { throw "There are no cards left." }
$Card = $this.Cards[0]
$this.Cards = $this.Cards[1..$this.Cards.Count]
$this.Dealt += $Card
return $Card
}
[string[]] Deal([int]$Count) {
if ($Count -gt $this.Cards.Count) {
throw "There are only $($this.Cards.Count) cards left."
} elseif ($Count -lt 1) {
throw "You must deal at least 1 card."
}
return (1..$Count | ForEach-Object { $this.Deal() })
}
}
方法輸出
根據預設,方法沒有任何輸出。 如果方法簽章包含 Void 以外的明確輸出類型,此方法必須傳回該類型的物件。 除非關鍵詞明確傳回 對象, return
否則方法不會發出任何輸出。
方法參數
類別方法可以定義在方法主體中使用的輸入參數。 方法參數會放在括號中,並以逗號分隔。 空括號表示方法不需要任何參數。
參數可以在單行或多行上定義。 下列區塊顯示方法參數的語法。
([[<parameter-type>]]$<parameter-name>[, [[<parameter-type>]]$<parameter-name>])
(
[[<parameter-type>]]$<parameter-name>[,
[[<parameter-type>]]$<parameter-name>]
)
方法參數可以強型別。 如果未輸入參數,方法會接受該參數的任何物件。 如果參數已輸入,方法會嘗試將該參數的值轉換成正確的類型,如果無法轉換輸入,則會擲回例外狀況。
方法參數無法定義預設值。 所有方法參數都是必要參數。
方法參數不能有任何其他屬性。 這可防止方法搭配 Validate*
屬性使用參數。 如需驗證屬性的詳細資訊,請參閱 about_Functions_Advanced_Parameters。
您可以使用下列其中一種模式,將驗證新增至方法參數:
- 使用必要的驗證屬性,將參數重新指派給相同的變數。 這適用於靜態和實例方法。 如需此模式的範例,請參閱 範例 4。
- 用來
Update-TypeData
定義ScriptMethod
直接在參數上使用驗證屬性的 。 這隻適用於實例方法。 如需詳細資訊,請參閱 使用Update-TypeData 定義實例方法一節。
方法中的自動變數
並非所有的自動變數都可以在方法中使用。 下列清單包含自動變數和建議,以瞭解如何在PowerShell類別方法中使用它們。 類別方法無法使用清單中未包含的自動變數。
$?
- 一般存取。$_
- 一般存取。$args
- 請改用明確的參數變數。$ConsoleFileName
- 改為$Script:ConsoleFileName
存取 。$Error
- 一般存取。$EnabledExperimentalFeatures
- 改為$Script:EnabledExperimentalFeatures
存取 。$Event
- 一般存取。$EventArgs
- 一般存取。$EventSubscriber
- 一般存取。$ExecutionContext
- 改為$Script:ExecutionContext
存取 。$false
- 一般存取。$foreach
- 一般存取。$HOME
- 改為$Script:HOME
存取 。$Host
- 改為$Script:Host
存取 。$input
- 請改用明確的參數變數。$IsCoreCLR
- 改為$Script:IsCoreCLR
存取 。$IsLinux
- 改為$Script:IsLinux
存取 。$IsMacOS
- 改為$Script:IsMacOS
存取 。$IsWindows
- 改為$Script:IsWindows
存取 。$LASTEXITCODE
- 一般存取。$Matches
- 一般存取。$MyInvocation
- 一般存取。$NestedPromptLevel
- 一般存取。$null
- 一般存取。$PID
- 改為$Script:PID
存取 。$PROFILE
- 改為$Script:PROFILE
存取 。$PSBoundParameters
- 請勿使用此變數。 其適用於 Cmdlet 和函式。 在類別中使用它可能會有非預期的副作用。$PSCmdlet
- 請勿使用此變數。 其適用於 Cmdlet 和函式。 在類別中使用它可能會有非預期的副作用。$PSCommandPath
- 一般存取。$PSCulture
- 改為$Script:PSCulture
存取 。$PSEdition
- 改為$Script:PSEdition
存取 。$PSHOME
- 改為$Script:PSHOME
存取 。$PSItem
- 一般存取。$PSScriptRoot
- 一般存取。$PSSenderInfo
- 改為$Script:PSSenderInfo
存取 。$PSUICulture
- 改為$Script:PSUICulture
存取 。$PSVersionTable
- 改為$Script:PSVersionTable
存取 。$PWD
- 一般存取。$Sender
- 一般存取。$ShellId
- 改為$Script:ShellId
存取 。$StackTrace
- 一般存取。$switch
- 一般存取。$this
- 一般存取。 在類別方法中,$this
一律是 類別的目前實例。 您可以使用它來存取類別屬性和方法。 靜態方法中無法使用。$true
- 一般存取。
如需自動變數的詳細資訊,請參閱 about_Automatic_Variables。
隱藏的方法
您可以使用 關鍵詞來宣告類別的方法,以隱藏這些 hidden
方法。
隱藏的類別方法包括:
- Cmdlet 所
Get-Member
傳回的類別成員清單中未包含。 若要使用Get-Member
顯示隱藏的方法,請使用 Force 參數。 - 除非完成發生在定義隱藏方法的類別中,否則不會顯示在 Tab 鍵自動完成或 IntelliSense 中。
- 類別的公用成員。 可以呼叫和繼承它們。 隱藏方法不會讓它成為私用。 它只會隱藏方法,如先前的點所述。
注意
當您隱藏方法的任何多載時,該方法會從 IntelliSense 移除、完成結果,以及的預設 Get-Member
輸出。
如需 關鍵詞的詳細資訊 hidden
,請參閱 about_Hidden。
靜態方法
您可以使用 關鍵詞宣告 方法 static
,將方法定義為屬於類別本身,而不是 類別的實例。 靜態類別方法:
- 一律可供使用,與類別具現化無關。
- 會在類別的所有實例之間共用。
- 一律可供使用。
- 無法存取 類別的實例屬性。 它們只能存取靜態屬性。
- 整個工作階段範圍的即時處理。
衍生類別方法
當類別衍生自基類時,它會繼承基類及其多載的方法。 在基類上定義的任何方法多載,包括隱藏的方法,都可以在衍生類別上使用。
衍生類別可以覆寫繼承的方法多載,方法是在類別定義中重新定義。 若要覆寫多載,參數類型必須與基類的 型別相同。 多載的輸出類型可能不同。
不同於建構函式,方法無法使用 語法來叫用 : base(<parameters>)
方法的基類多載。 衍生類別上重新定義的多載會完全取代基類所定義的多載。
下列範例顯示衍生類別上靜態和實例方法的行為。
基類定義:
- 傳回目前時間和
DaysAgo()
傳回過去日期的靜態方法Now()
。 - 實例屬性 TimeStamp 和
ToString()
傳回該屬性字串表示的實例方法。 這可確保當實例用於字串時,它會轉換成 datetime 字串,而不是類別名稱。 - 具有兩個多載的實例方法
SetTimeStamp()
。 在沒有參數的情況下呼叫 方法時,它會將 TimeStamp 設定為目前的時間。 使用 DateTime 呼叫 方法時,它會將 TimeStamp 設定為該值。
class BaseClass {
static [datetime] Now() {
return Get-Date
}
static [datetime] DaysAgo([int]$Count) {
return [BaseClass]::Now().AddDays(-$Count)
}
[datetime] $TimeStamp = [BaseClass]::Now()
[string] ToString() {
return $this.TimeStamp.ToString()
}
[void] SetTimeStamp([datetime]$TimeStamp) {
$this.TimeStamp = $TimeStamp
}
[void] SetTimeStamp() {
$this.TimeStamp = [BaseClass]::Now()
}
}
下一個區塊會定義衍生自 BaseClass的類別:
- DerivedClassA 繼承自 BaseClass ,沒有任何覆寫。
- DerivedClassB 會
DaysAgo()
覆寫靜態方法,以傳回字串表示法,而不是 DateTime 物件。 它也會覆寫ToString()
實例方法,以將時間戳當做ISO8601日期字元串傳回。 - DerivedClassC 會覆寫 方法的
SetTimeStamp()
無參數多載,讓不含參數的時間戳設定為目前日期之前的 10 天。
class DerivedClassA : BaseClass {}
class DerivedClassB : BaseClass {
static [string] DaysAgo([int]$Count) {
return [BaseClass]::DaysAgo($Count).ToString('yyyy-MM-dd')
}
[string] ToString() {
return $this.TimeStamp.ToString('yyyy-MM-dd')
}
}
class DerivedClassC : BaseClass {
[void] SetTimeStamp() {
$this.SetTimeStamp([BaseClass]::Now().AddDays(-10))
}
}
下列區塊顯示所定義類別之靜態 Now()
方法的輸出。 每個類別的輸出都相同,因為衍生類別不會覆寫 方法的基類實作。
"[BaseClass]::Now() => $([BaseClass]::Now())"
"[DerivedClassA]::Now() => $([DerivedClassA]::Now())"
"[DerivedClassB]::Now() => $([DerivedClassB]::Now())"
"[DerivedClassC]::Now() => $([DerivedClassC]::Now())"
[BaseClass]::Now() => 11/06/2023 09:41:23
[DerivedClassA]::Now() => 11/06/2023 09:41:23
[DerivedClassB]::Now() => 11/06/2023 09:41:23
[DerivedClassC]::Now() => 11/06/2023 09:41:23
下一個區塊會呼叫 DaysAgo()
每個類別的靜態方法。 只有 DerivedClassB 的輸出不同,因為它會覆寫基底實作。
"[BaseClass]::DaysAgo(3) => $([BaseClass]::DaysAgo(3))"
"[DerivedClassA]::DaysAgo(3) => $([DerivedClassA]::DaysAgo(3))"
"[DerivedClassB]::DaysAgo(3) => $([DerivedClassB]::DaysAgo(3))"
"[DerivedClassC]::DaysAgo(3) => $([DerivedClassC]::DaysAgo(3))"
[BaseClass]::DaysAgo(3) => 11/03/2023 09:41:38
[DerivedClassA]::DaysAgo(3) => 11/03/2023 09:41:38
[DerivedClassB]::DaysAgo(3) => 2023-11-03
[DerivedClassC]::DaysAgo(3) => 11/03/2023 09:41:38
下列區塊顯示每個類別之新實例的字串表示。 DerivedClassB 的表示法不同,因為它會覆ToString()
寫實例方法。
"`$base = [BaseClass]::New() => $($base = [BaseClass]::New(); $base)"
"`$a = [DerivedClassA]::New() => $($a = [DerivedClassA]::New(); $a)"
"`$b = [DerivedClassB]::New() => $($b = [DerivedClassB]::New(); $b)"
"`$c = [DerivedClassC]::New() => $($c = [DerivedClassC]::New(); $c)"
$base = [BaseClass]::New() => 11/6/2023 9:44:57 AM
$a = [DerivedClassA]::New() => 11/6/2023 9:44:57 AM
$b = [DerivedClassB]::New() => 2023-11-06
$c = [DerivedClassC]::New() => 11/6/2023 9:44:57 AM
下一個區塊會針對每個實例呼叫 SetTimeStamp()
實例方法,將 TimeStamp 屬性設定為特定日期。 每個實例都有相同的日期,因為沒有任何衍生類別會覆寫 方法的參數化多載。
[datetime]$Stamp = '2024-10-31'
"`$base.SetTimeStamp(`$Stamp) => $($base.SetTimeStamp($Stamp) ; $base)"
"`$a.SetTimeStamp(`$Stamp) => $($a.SetTimeStamp($Stamp); $a)"
"`$b.SetTimeStamp(`$Stamp) => $($b.SetTimeStamp($Stamp); $b)"
"`$c.SetTimeStamp(`$Stamp) => $($c.SetTimeStamp($Stamp); $c)"
$base.SetTimeStamp($Stamp) => 10/31/2024 12:00:00 AM
$a.SetTimeStamp($Stamp) => 10/31/2024 12:00:00 AM
$b.SetTimeStamp($Stamp) => 2024-10-31
$c.SetTimeStamp($Stamp) => 10/31/2024 12:00:00 AM
最後一個區塊會呼叫 SetTimeStamp()
不含任何參數。 輸出會顯示 DerivedClassC 實例的值設定為 10 天前的其他實例。
"`$base.SetTimeStamp() => $($base.SetTimeStamp() ; $base)"
"`$a.SetTimeStamp() => $($a.SetTimeStamp(); $a)"
"`$b.SetTimeStamp() => $($b.SetTimeStamp(); $b)"
"`$c.SetTimeStamp() => $($c.SetTimeStamp(); $c)"
$base.SetTimeStamp() => 11/6/2023 9:53:58 AM
$a.SetTimeStamp() => 11/6/2023 9:53:58 AM
$b.SetTimeStamp() => 2023-11-06
$c.SetTimeStamp() => 10/27/2023 9:53:58 AM
使用 Update-TypeData 定義實例方法
除了直接在類別定義中宣告方法之外,您還可以使用 Update-TypeData
Cmdlet 在靜態建構函式中定義類別實例的方法。
使用此代碼段作為模式的起點。 視需要取代角括弧中的佔位元文字。
class <ClassName> {
static [hashtable[]] $MemberDefinitions = @(
@{
MemberName = '<MethodName>'
MemberType = 'ScriptMethod'
Value = {
param(<method-parameters>)
<method-body>
}
}
)
static <ClassName>() {
$TypeName = [<ClassName>].Name
foreach ($Definition in [<ClassName>]::MemberDefinitions) {
Update-TypeData -TypeName $TypeName @Definition
}
}
}
提示
Cmdlet Add-Member
可以將屬性和方法新增至非靜態建構函式中的類別,但 Cmdlet 會在每次呼叫建構函式時執行。 在 Update-TypeData
靜態建構函式中使用 可確保將成員加入類別的程式代碼只需要在會話中執行一次。
使用預設參數值和驗證屬性定義方法
直接在類別宣告中定義的方法無法定義方法參數上的預設值或驗證屬性。 若要使用預設值或驗證屬性定義類別方法,必須將它們定義為 ScriptMethod 成員。
在此範例中,CardDeck 類別會定義方法Draw()
,該方法會同時使用驗證屬性和 Count 參數的預設值。
class CookieJar {
[int] $Cookies = 12
static [hashtable[]] $MemberDefinitions = @(
@{
MemberName = 'Eat'
MemberType = 'ScriptMethod'
Value = {
param(
[ValidateScript({ $_ -ge 1 -and $_ -le $this.Cookies })]
[int] $Count = 1
)
$this.Cookies -= $Count
if ($Count -eq 1) {
"You ate 1 cookie. There are $($this.Cookies) left."
} else {
"You ate $Count cookies. There are $($this.Cookies) left."
}
}
}
)
static CookieJar() {
$TypeName = [CookieJar].Name
foreach ($Definition in [CookieJar]::MemberDefinitions) {
Update-TypeData -TypeName $TypeName @Definition
}
}
}
$Jar = [CookieJar]::new()
$Jar.Eat(1)
$Jar.Eat()
$Jar.Eat(20)
$Jar.Eat(6)
You ate 1 cookie. There are 11 left.
You ate 1 cookie. There are 10 left.
MethodInvocationException:
Line |
36 | $Jar.Eat(20)
| ~~~~~~~~~~~~
| Exception calling "Eat" with "1" argument(s): "The attribute
| cannot be added because variable Count with value 20 would no
| longer be valid."
You ate 6 cookies. There are 4 left.
注意
雖然此模式適用於驗證屬性,但請注意例外狀況會誤導,並參考無法新增屬性。 明確檢查參數的值並改為引發有意義的錯誤,可能是較佳的用戶體驗。 如此一來,使用者可以了解為什麼他們看到錯誤,以及該怎麼做。
限制
PowerShell 類別方法有下列限制:
方法參數無法使用任何屬性,包括驗證屬性。
因應措施:使用驗證屬性重新指派方法主體中的參數,或使用 Cmdlet 在靜態建構
Update-TypeData
函式中定義 方法。方法參數無法定義預設值。 參數一律為必要參數。
因應措施:使用
Update-TypeData
Cmdlet 在靜態建構函式中定義 方法。方法一律為公用,即使它們已隱藏也一樣。 繼承類別時可以覆寫它們。
因應措施:無。
如果隱藏方法的任何多載,該方法的每個多載也會被視為隱藏。
因應措施:無。