关于类
简短说明
介绍如何使用类创建自己的自定义类型。
长说明
PowerShell 5.0 添加了一个正式语法来定义类和其他用户定义的类型。 添加类使开发人员和 IT 专业人员能够将 PowerShell 用于更广泛的用例。 它简化了 PowerShell 项目的开发,并加速了管理图面的覆盖。
类声明是用于在运行时创建对象的实例的蓝图。 定义类时,类名是类型的名称。 例如,如果声明名为 Device 的类并将变量 $dev
初始化为 Device 的新实例, $dev
则 是 Device 类型的对象或实例。 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 = "Microsoft"
$dev
Brand
-----
Microsoft
类属性
属性是在类范围内声明的变量。 属性可以是任何内置类型,也可以是另一个类的实例。 类对它们拥有的属性数没有限制。
具有简单属性的示例类
class Device {
[string]$Brand
[string]$Model
[string]$VendorSku
}
$device = [Device]::new()
$device.Brand = "Microsoft"
$device.Model = "Surface Pro 4"
$device.VendorSku = "5072641000"
$device
Brand Model VendorSku
----- ----- ---------
Microsoft Surface Pro 4 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...}
类方法
方法定义类可以执行的操作。 方法可能采用提供输入数据的参数。 方法可以返回输出。 方法返回的数据可以是任何定义的数据类型。
具有属性和方法的简单类示例
扩展 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()
$surface = [Device]::new()
$surface.Brand = "Microsoft"
$surface.Model = "Surface Pro 4"
$surface.VendorSku = "5072641000"
$rack.AddDevice($surface, 2)
$rack
$rack.GetAvailableSlots()
Slots : 8
Brand :
Model :
VendorSku :
AssetId :
Devices : {$null, $null, Microsoft|Surface Pro 4|5072641000, $null...}
0
1
3
4
5
6
7
类方法中的输出
方法应定义返回类型。 如果方法不返回输出,则输出类型应为 [void]
。
在类方法中,除了 语句中 return
提到的对象之外,不会向管道发送任何对象。 代码不会意外输出到管道。
注意
这与 PowerShell 函数处理输出的方式基本不同,因为输出一切都会进入管道。
方法输出
此示例演示除了 语句之外, 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 值。 定义构造函数时,不会创建默认的无参数构造函数。 如果需要,Create无参数构造函数。
构造函数基本语法
在此示例中,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]$surface = [Device]::new("Microsoft", "Surface Pro 4", "5072641000")
$surface
Brand Model VendorSku
----- ----- ---------
Microsoft Surface Pro 4 5072641000
具有多个构造函数的示例
在此示例中, Device 类使用属性、默认构造函数和用于初始化实例的构造函数进行定义。
默认构造函数将 品牌 设置为 Undefined,并将 model 和 vendor-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]$surface = [Device]::new("Microsoft", "Surface Pro 4", "5072641000")
$somedevice
$surface
Brand Model VendorSku
----- ----- ---------
Undefined
Microsoft Surface Pro 4 5072641000
隐藏属性
特性 hidden
使属性或方法不那么可见。 属性或方法仍可供用户访问,并在对象可用的所有范围内可用。 隐藏成员在 cmdlet 中 Get-Member
隐藏,不能在类定义之外使用 Tab 补全或 IntelliSense 显示。
使用隐藏属性的示例
创建 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("Microsoft", "Surface Pro 4", 16)
$r1
$r1.Devices.Length
$r1.Slots
Brand Model Devices
----- ----- -------
Microsoft Surface Pro 4 {$null, $null, $null, $null...}
16
16
请注意 ,输出 中 $r1
未显示 Slots 属性。 但是,构造函数更改了大小。
静态属性
特性 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
请注意,每次运行此示例时,机架数都会增加。
属性验证特性
通过验证属性,可以测试给定给属性的值是否满足定义的要求。 在分配值的那一刻触发验证。 请参阅 about_functions_advanced_parameters。
使用验证属性的示例
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 is not 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
PowerShell 类中的继承
可以通过创建派生自现有类的新类来扩展类。 派生类继承基类的属性。 可以根据需要添加或替代方法和属性。
PowerShell 不支持多重继承。 类不能从多个类继承。 但是,可以使用接口实现此目的。
继承实现由 :
运算符定义;这意味着扩展此类或实现这些接口。 派生类应始终在类声明中最左侧。
使用简单继承语法的示例
此示例演示简单的 PowerShell 类继承语法。
Class Derived : Base {...}
此示例演示基类后面的接口声明的继承。
Class Derived : Base.Interface {...}
PowerShell 类中的简单继承示例
在此示例中,前面示例中使用的 Rack和 Device 类被更好地定义为:避免属性重复,更好地对齐通用属性,并重用通用业务逻辑。
数据中心中的大多数对象都是公司资产,因此开始将其作为资产进行跟踪是有意义的。 设备类型由 DeviceType
枚举定义,有关枚举的详细信息,请参阅 about_Enum 。
在我们的示例中,我们只定义 Rack
和 ComputeServer
;这两个扩展都对 Device
类。
enum DeviceType {
Undefined = 0
Compute = 1
Storage = 2
Networking = 4
Communications = 8
Power = 16
Rack = 32
}
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
关键字 (keyword) 。
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
接口
声明接口的语法类似于 C#。 在没有指定基类型时,可以在基类型之后声明接口,也可以在冒号 (:
) 之后声明接口。 用逗号分隔所有类型名称。
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
语句将失败。