技术笔记PowerShell 面向对象
狂犬主子PowerShell 面向对象
PowerShell 是一种强大的脚本语言,主要用于基于 Windows 的系统管理自动化。它支持面向对象编程(OOP),允许开发者创建类、实例化对象以及使用继承等特性来构建复杂的程序结构。
PowerShell 从版本 5.0 开始支持类的定义和其他用户定义类型的正式语法。这允许开发人员和 IT 专业人士使用面向对象编程的概念来定义自定义类型,从而扩大 PowerShell 的适用范围。
本文为个人的学习笔记,参考官方文档,加入了一些理解,示例为本人实验时写的代码,可能不太严谨。如果有错误内容的话,还请多多指正!
概念
类(Class) 是抽象的模板,定义了对象的结构和行为。
对象(Object) 是根据类(Class)创建出来的实例(Instance),对象是类的具体实例。
实例化是根据类(Class)创建对象(Object)的过程。
实例成员是类(Class)的实例属性和实例方法,其中包含多个属性(Property)、构造函数(Constructor)、和方法(Method),它们的状态随着每个对象实例的不同而不同。
每个实例都有独立的成员,各个实例的同名字段互不影响。
也就是说,每个对象实例都有其自己的一套实例成员的副本。
只能通过类的创建实例访问属性和方法。
类属性(Class_Property) 是在类范围中声明的变量。
属性可以是任何内置类型,也可以是另一个类的实例。
类可以具有零个或多个属性。
类属性没有数量限制。
类构造函数(Class_Constructor) 是用于根据类(Class)初始化新创建的对象(Object)的函数。
构造函数使你能够在创建类实例时设置默认值并验证对象逻辑。
构造函数与类具有相同的名称。
构造函数可能具有参数来初始化新对象的数据成员。
限制:
- 构造函数没有输出类型。 他们无法使用
return
返回值。
- 构造函数始终与类同名。
- 无法直接调用构造函数。 它们仅在创建实例时运行。
- 构造函数永远不会出现在 cmdlet 的
Get-Member
输出中。
一个类可以定义零个或多个构造函数。
如果未定义构造函数,则为该类提供默认的无参数构造函数。此构造函数将所有成员初始化为其默认值。 对象类型和字符串被赋予 null 值。
定义构造函数时,不会创建默认的无参数构造函数。如果需要,请创建无参数构造函数。
类方法(Class_Methods) 是定义类可以执行的操作(函数)。
方法可以采用指定输入数据的参数。
方法始终定义输出类型。
如果方法未返回任何输出,则它必须具有 Void 输出类型。 如果方法未显式定义输出类型,则该方法的输出类型为 Void。
在类方法中,除语句中指定的 return
对象外,不会向管道发送任何对象。代码不会意外输出到管道。这与 PowerShell 函数处理输出的方式有根本的不同,在 PowerShell 中,所有内容都进入管道。
hidden
关键字 用于隐藏类成员。
该成员仍可供用户访问,并且可在对象可用的所有范围内使用。
隐藏成员在 Get-Member
cmdlet 中隐藏(可以用 Get-Member -Force
显示),不能在类定义之外使用 Tab 补全或 IntelliSense 显示。
static
关键字仅适用于类成员,而不适用于类本身。
static
关键字 用于定义类成员为静态。
静态属性始终可用,独立于类实例化。也就是说,静态属性可以在不创建对象的情况下直接访问。
静态属性在类的所有实例之间共享。也就是说,静态属性可以作为全局变量使用。
静态方法始终可用。也就是说,静态方法可以在不创建对象的情况下直接调用。
所有静态属性在整个会话范围内都有效。
自动 $this
变量在静态方法中不可用。
PowerShell 中定义的类的静态属性是可修改的(Update-TypeData
cmdlet),默认情况下是不可修改的。无法定义不可变的静态属性。
static
关键字仅适用于类成员,而不适用于类本身。
继承(Class_Inheritance) 是一种机制,它允许一个类继承另一个类的属性和方法。
PowerShell类支持继承,这使你可以定义可重用(继承)、扩展或修改父类行为的子类。
成员被继承的类称为基类(父类)。 继承基类成员的类称为派生类(子类)。可以通过创建派生自现有类的新类来扩展类。 派生类继承基类的属性和方法。可以根据需要添加或替代基类成员。
PowerShell 仅支持单一继承。 类只能继承自单个类。 不过,继承是可传递的。这样一来,就可以为一组类型定义继承层次结构。 换句话说,类型 D 可以继承自类型 C,该类型继承自类型 B,后者继承自基类类型 A。由于继承是可传递的,A 类型的成员可用于类型 D。
派生类可以访问基类的所有成员,但无法访问基类的私有成员。
派生类不会继承基类的所有成员:不会继承静态构造函数、实例构造函数。
语法
定义类
1 2 3 4 5 6
| class <类名> [ : [ <基类> ][ , <接口列表> ]] { [[<属性>] [hidden] [static] <属性定义> ...] [<类名>([<构造函数参数列表>]) {<构造函数语句列表>} ...] [[<属性>] [hidden] [static] <方法定义> ...] }
|
说明:
- 类名:定义一个类的名字。定义类名一般使用首字母大写的驼峰式命名。
- 基类:可选,指定该类继承自哪个基类。
- 接口列表:可选,指定该类实现哪些接口。
- 属性:修饰符,如访问级别控制(public, private, protected)或其他特性。
- hidden:表示隐藏父类中的同名成员。
- static:表示静态成员,不属于任何实例。
- 属性定义:定义类的属性或字段。
- 构造函数:定义类的构造函数,用于初始化新创建的对象。
- 构造函数参数列表:构造函数接受的参数列表。
- 构造函数语句列表:构造函数内部执行的语句。
- 方法定义:定义类的方法。
实例化类
New-Object
cmdlet:
1 2
| [$<变量名> =] New-Object -TypeName <类名> [ [-ArgumentList] <构造函数参数列表>]
|
- 变量名:创建的对象将被赋值给这个变量。
- New-Object:创建一个新的对象实例。
- -TypeName:指定要创建的对象的类名。
- -ArgumentList:可选参数列表,用于传递给构造函数。
**[类名]::new()
**:
1
| [$<变量名> =] [<类名>]::new([<构造函数参数列表>])
|
- 变量名:创建的对象将被赋值给这个变量。
- ::new:调用类的构造函数。
- 构造函数参数列表:构造函数的参数列表。
使用 [类名]::new()
语法时,类名两边必须有括号([]
和()
)。括号表示 PowerShell 的类型定义。
类名哈希表:
1
| [$<变量名> =] [<类名>]@{[<类属性哈希表>]}
|
- 变量名:创建的对象将被赋值给这个变量。
- 类名:类名。
- 类属性哈希表:使用哈希表来初始化类的属性。
哈希表语法仅适用于具有不需要任何参数的默认构造函数的类。 它使用默认构造函数创建类的实例,然后将键值对分配给实例属性。 如果哈希表中的任何键不是有效的属性名称,PowerShell 将引发错误。
类属性
单行语法
1
| [[<属性>]...] [<属性类型>] $<属性名称> [= <默认值>]
|
多行语法
1 2 3
| [[<属性>]...] [<属性类型>] $<属性名称> [= <默认值>]
|
- 属性(Attributes):
[[<attribute>]...]
:表示一个或多个属性(attributes)。这些属性可以用于修饰属性,提供额外的信息或行为。
- 例如:
[NoRunspaceAffinity()]
或 [ValidateRange(1, 10)]
。
- 属性类型(Property Type):
<property-type>
:表示属性的数据类型。
- 例如:
[int]
、[string]
、[DateTime]
等。
- 属性名称(Property Name):
$<property-name>
:表示属性的名称。
- 例如:
$MyProperty
。
- 默认值(Default Value)(可选):
= <default-value>
:表示属性的默认值。
- 例如:
= 0
、= "default"
等。
类方法
单行语法
1
| [[<属性>]...] [hidden] [static] [<输出类型>] <方法名称> ([<方法参数>]) { <方法体> }
|
多行语法
1 2 3 4 5 6
| [[<属性>]...] [hidden] [static] [<输出类型>] <方法名称> ([<方法参数>]) { <方法体> }
|
- 属性(Attributes):
[[<attribute>]...]
:表示一个或多个属性(attributes)。这些属性可以用于修饰方法,提供额外的信息或行为。
- 例如:
[NoRunspaceAffinity()]
或 [ValidateRange(1, 10)]
。
- 隐藏(Hidden):
[hidden]
:表示该方法是隐藏的,不会显示在对象的公共成员列表中。
- 静态(Static):
[static]
:表示该方法是静态方法,可以直接通过类名调用,不需要创建类的实例。
- 输出类型(Output Type):
[<output-type>]
:表示方法的返回类型。
- 例如:
[int]
、[string]
、[void]
等。
- 方法名称(Method Name):
<method-name>
:表示方法的名称。
- 例如:
MyMethod
。
- 方法参数(Method Parameters):
([<method-parameters>])
:表示方法的参数列表。
- 例如:
([int] $param1, [string] $param2)
。
- 方法体(Body):
{ <body> }
:表示方法的具体实现代码块。
- 例如:
{ Write-Host "Hello, World!" }
。
示例
最小定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class MadDog { [string]$Name }
$dog = [MadDog]::new()
$dog.Name = "Yaya"
$dog
Name ---- Yaya
|
包含实例成员的类
此示例定义了一个 MadDog 类,其中包含多个属性、构造函数和方法。 每个定义的成员都是实例成员,而不是静态成员。只能通过类的创建实例访问属性和方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class MadDog { [string]$Name [datetime]$Birthday
MadDog() { $this.Init(@{}) }
MadDog([hashtable]$Properties) { $this.Init($Properties) }
MadDog([string]$Name, [datetime]$Birthday) { $this.Init(@{Name = $Name; Birthday = $Birthday }) }
[void] Init([hashtable]$Properties) { foreach ($Property in $Properties.Keys) { $this.$Property = $Properties.$Property } }
[timespan] GetAge() { if ( $null -eq $this.Birthday -or $this.Birthday -eq [datetime]::MinValue ) { throw "Birthday 未定义" } return (Get-Date) - $this.Birthday }
[string] ToString() { return "$($this.Name) 于 $($this.Birthday.Year) 年 $($this.Birthday.Month) 月 $($this.Birthday.Day) 日生,现在已经生活了 $($this.GetAge()) 天。" } }
|
实例化 MadDog 类的 dog 对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| $dog = [MadDog]::new("Yaya", "2017-01-01")
$dog
$dog.GetAge()
$dog.ToString()
|
具有静态成员的类
此示例中的 DogList 类基于示例 2 中的 MadDog 类。虽然 DogList 类不能标记为静态本身,但实现仅定义 Dogs 静态属性和一组用于管理该属性的静态方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| class DogList { static [System.Collections.Generic.List[MadDog]] $Dogs
static [void] Initialize() { [DogList]::Initialize($false) }
static [bool] Initialize([bool]$force) { if ([DogList]::Dogs.Count -gt 0 -and -not $force) { return $false }
[DogList]::Dogs = [System.Collections.Generic.List[MadDog]]::new()
return $true }
static [void] Validate([MadDog]$Dog) { $Prefix = @( '狂犬验证失败:书籍必须定义 Name 和 Birthday 属性,但是' ) -join ' ' if ($null -eq $Dog.Name) { throw "$Prefix Name 属性未定义。" } if ($null -eq $Dog.Birthday) { throw "$Prefix Birthday 属性未定义。" } }
static [void] Add([MadDog]$Dog) { [DogList]::Initialize() [DogList]::Validate($Dog) if ([DogList]::Dogs.Contains($Dog)) { throw "狂犬 $Dog 已存在于列表中。" }
[DogList]::Dogs.Add($Dog) }
static [void] Clear() { [DogList]::Initialize($true) [DogList]::Dogs.Clear() }
static [MadDog] Find([scriptblock]$Filter) { [DogList]::Initialize() return [DogList]::Dogs.Find($Filter) }
static [MadDog[]] FindAll([scriptblock]$Filter) { [DogList]::Initialize() return [DogList]::Dogs.FindAll($Filter) }
static [void] Remove([MadDog]$Dog) { [DogList]::Initialize() [DogList]::Dogs.Remove($Dog) }
static [void] RemoveBy([string]$Property, [object]$Value) { [DogList]::Initialize() $Index = [DogList]::Dogs.FindIndex({ $args[0].$Property -eq $Value }.GetNewClosure()) if ($Index -ge 0) { [DogList]::Dogs.RemoveAt($Index) } } }
|
定义 DogList 类后,可以将上一示例中的狂犬添加到列表中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| $null -eq [DogList]::Dogs
[DogList]::Add($dog)
[DogList]::Add($dog)
[DogList]::Dogs
|
以下代码片段调用类的静态方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| [DogList]::Add([MadDog]::new(@{ Name = "ToWong" Birthday = "2015-01-01" }))
[DogList]::Find({ $args[0].Birthday -gt '2016-01-01' }).ToString()
[DogList]::FindAll({ $args[0].Birthday -gt '2014-01-01' }).Name
[DogList]::Remove($dog) [DogList]::Dogs.Name
[DogList]::RemoveBy('Name', 'ToWong') "Names: $([DogList]::Dogs.Name)"
[DogList]::Add($dog) [DogList]::Add($dog)
|
包含和不使用 Runspace 相关性的类定义
PowerShell 允许定义类,而不考虑它们是否与当前运行空间相关。下面的示例定义了一个类,该类可以与任何 PowerShell 运行空间相关。
UnsafeClass
示例
ShowRunspaceId()
报告不同的线程 ID,但相同的运行空间 ID 的方法 [UnsafeClass]
。 最终,会话状态已损坏,导致错误,例如 Global scope cannot be removed
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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 可停止执行。
SafeClass
示例
[SafeClass]
的 ShowRunspaceId()
方法报告不同的线程 Runspace ID。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| [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 可停止执行。
具有自定义类型的类属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| enum LogLevel { Debug Info Warning Error } class Logger { [string]$Message [LogLevel]$Level = [LogLevel]::Info [void] Write() { Write-Host "[$($this.Level)]: $($this.Message)" } }
$logger = [Logger]::new() $logger.Message = "Hello, World!" $logger.Level = [LogLevel]::Debug $logger.Write()
|
具有验证类型的类属性
using namespace System.Management.Automation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class InstallPackage { [ValidateNotNullOrEmpty()] [string]$Path }
$installer = [InstallPackage]::new()
$installer.Path = ""
$installer.Path = $null
$installer.Path = "C:\Program Files" $installer
|
具有显式默认值的类属性
1 2 3 4 5 6 7 8 9 10 11 12
| class MadDog { [string]$Name = "Yaya" [DateTime]$Birthday = (Get-Date).Date }
[MadDog]::new()
[MadDog]::new().Birthday -eq (Get-Date).Date
|
隐藏类属性
不支持 PowerShell 5.1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class MadDog { [string]$Name = "Yaya" [DateTime]$Birthday = (Get-Date).Date hidden [string]$Guid = (New-Guid).Guid }
$dog = [MadDog]::new()
$dog
$dog.Guid
$dog | Get-Member -MemberType Properties
$dog | Get-Member -MemberType Properties -Force
|
Static 类属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class MadDog { [string]$Name [DateTime]$Birthday = (Get-Date).Date hidden [string]$Guid = (New-Guid).Guid static [MadDog[]]$Dogs = @() MadDog() { [MadDog]::Dogs += $this } }
"Dog Count: $([MadDog]::Dogs.Count)"
$dog1 = [MadDog] @{ Name = "Yaya" } $dog2 = [MadDog] @{ Name = "ToWong" }
[MadDog]::Dogs | Select-Object -Property Name, Guid
|
派生类属性(继承)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class RuralDog { static [string]$Breed = "Rural" [string]$Name = "XiaoHua" }
class ChineseDog : RuralDog {}
class Akita : RuralDog { [string]$Name = "Sakura" }
class Wolf : RuralDog { [string] $Breed [string] $Name }
class MadDog : ChineseDog { static [string]$Breed = "SuperRural" [string]$Name = "Yaya" }
"土狗: $([RuralDog]::new().Name) - $([RuralDog]::Breed) "
"中华田园犬: $([ChineseDog]::new().Name) - $([ChineseDog]::Breed) "
"秋田犬: $([Akita]::new().Name) - $([Akita]::Breed) "
"狼: $([Wolf]::new().Name) - $([Wolf]::Breed) "
"狂犬: $([MadDog]::new().Name) - $([MadDog]::Breed) "
|
具有参数的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class McDog { [float] $长 [float] $宽 [float] $高
[float] 获取体积() { return $this.长 * $this.宽 * $this.高 }
[float] 获取质量([float]$密度) { return $this.获取体积() * $密度 } }
$Mc_YDog = [McDog]@{ 长 = 2 宽 = 2 高 = 3 }
$Mc_YDog.获取质量(1.25)
|
不带输出的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class McDog { [float] $长 [float] $宽 [float] $高
[float] 获取体积() { $this.检验尺寸() return $this.长 * $this.宽 * $this.高 }
[void] 检验尺寸() { $不合规的属性 = @() foreach ($属性 in @("长", "宽", "高")) { if ($this.$属性 -lt 0) { $不合规的属性 += $属性 } }
if ($不合规的属性.Count -gt 0) { $错误信息 = @( '不合规的属性' "('$($不合规的属性 -join "', '")'):" "狗的尺寸必须为正数!" ) -join ' ' throw $错误信息 } } }
$Mc_YDog = [McDog]@{ 长 = 2 宽 = -1 }
$Mc_YDog
$Mc_YDog.获取体积()
|
具有重载的静态方法
略
方法签名和重载
略